If you don’t already have CocoaPods installed, follow the steps in the CocoaPods Getting Started guide.
Open a terminal window and navigate to the location of your app’s Xcode project.
If you have not already created a Podfile for your application, create one now:
pod init
Open the Podfile created for your application and add the following:
pod 'GoogleSignIn'
If you are using SwiftUI, also add the pod extension for the “Sign in with Google” button:
pod 'GoogleSignInSwiftSupport'
Save the file and run:
pod install
From now on Open the generated .xcworkspaceworkspace file for your application in Xcode. Use this file for all future development on your application. (Note that this is different from the included .xcodeprojproject file, which would result in build errors when opened.)
Now we are almost ready to start coding, but when we build the project we might (or might not depends of X-code version) face some issues..
Navigate to the Build Settings, find ‘User Script Sandboxing’ and
Flip it to No
Fixing “Your app is missing support for the following URL schemes:”
Copy missing scheme from the error message and add it in the info->url section
Let’s get started
Adding Google Client ID (GIDClientID)
Ether you face the problems before or not this is one thing that is mandatory.
1. Adding UserAuthModel to share between all views.
If you don’t know how to do this read about ObservableObject and @Published and sharing data between Views.
This class has to conform to the ObservableObject in order to have its properties reflecting the View.
We will create methods to check if user is signed in, and update shared parameters: givenName, userEmail, isLoggedIn …
The purpose of authentication on the backend server is to make sure that logged-in users could have access to some protected content, like subscriptions, pro-articles, etc.
Once the user signs-in in the native app, the app sends the id-token to the backend, and the backend validates the token and could return access-token back to the app.
In the previous chapter we added UserAuthModel.swift file.
This is the place to call the backend server.
let task = URLSession.shared.uploadTask(with: request, from: authData){ data, response, error in
print(response ?? ".")
// handle response from my backend.
if error != nil{
print("Error: \(String(describing: error))")
}
// Handle the response from the server
let dataString = String(data: data!, encoding: .utf8)
print("got data: \(dataString!)")
}
task.resume()
}
}
import SwiftUI
import GoogleSignIn
import GoogleSignInSwift
final class UserAuthModel: ObservableObject {
@Published var givenName: String = ""
@Published var isLoggedIn: Bool = false
@Published var errorMessage: String = ""
@Published var userEmail: String = ""
@Published var profilePicUrl: String = ""
init() {
check()
}
func getUserStatus() {
if GIDSignIn.sharedInstance.currentUser != nil {
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else { return }
let givenName = user.profile?.givenName
self.givenName = givenName ?? ""
self.userEmail = user.profile!.email
self.profilePicUrl = user.profile!.imageURL(withDimension: 100)!.absoluteString
self.isLoggedIn = true
} else {
self.isLoggedIn = false
self.givenName = "Not Logged In"
}
}
func check() {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if let error = error {
self.errorMessage = "error: \(error.localizedDescription)"
}
self.getUserStatus()
}
}
func gertRootViewController() -> UIViewController {
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
func signIn() {
GIDSignIn.sharedInstance.signIn(withPresenting: gertRootViewController()) { signInResult, error in
guard let result = signInResult else {
// Inspect error
print("Error occured in signIn()")
return
}
print("Signing in ...")
print(result.user.profile?.givenName ?? "")
self.getUserStatus()
self.sendTokenToBackendServer()
}
}
func signOut() {
GIDSignIn.sharedInstance.signOut()
self.getUserStatus()
}
func sendTokenToBackendServer() {
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else { return }
let stringToken = user.idToken!.tokenString
guard let authData = try? JSONEncoder().encode(["idToken" : stringToken]) else {
return
}
let url = URL(string: "https://regexor.net/examples/google-sign-in-server-notification/")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.uploadTask(with: request, from: authData) { data, response, error in
print(response ?? ".")
// handle response from my backend.
if error != nil {
print("Error: \(String(describing: error))")
}
// Handle the response from the server
let dataString = String(data: data!, encoding: .utf8)
print ("got data: \(dataString!)")
}
task.resume()
}
}
import SwiftUI
import GoogleSignIn
import GoogleSignInSwift
final class UserAuthModel: ObservableObject {
@Published var givenName: String = ""
@Published var isLoggedIn: Bool = false
@Published var errorMessage: String = ""
@Published var userEmail: String = ""
@Published var profilePicUrl: String = ""
init() {
check()
}
func getUserStatus() {
if GIDSignIn.sharedInstance.currentUser != nil {
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else { return }
let givenName = user.profile?.givenName
self.givenName = givenName ?? ""
self.userEmail = user.profile!.email
self.profilePicUrl = user.profile!.imageURL(withDimension: 100)!.absoluteString
self.isLoggedIn = true
} else {
self.isLoggedIn = false
self.givenName = "Not Logged In"
}
}
func check() {
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if let error = error {
self.errorMessage = "error: \(error.localizedDescription)"
}
self.getUserStatus()
}
}
func gertRootViewController() -> UIViewController {
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
func signIn() {
GIDSignIn.sharedInstance.signIn(withPresenting: gertRootViewController()) { signInResult, error in
guard let result = signInResult else {
// Inspect error
print("Error occured in signIn()")
return
}
print("Signing in ...")
print(result.user.profile?.givenName ?? "")
self.getUserStatus()
self.sendTokenToBackendServer()
}
}
func signOut() {
GIDSignIn.sharedInstance.signOut()
self.getUserStatus()
}
func sendTokenToBackendServer() {
let user = GIDSignIn.sharedInstance.currentUser
guard let user = user else { return }
let stringToken = user.idToken!.tokenString
guard let authData = try? JSONEncoder().encode(["idToken" : stringToken]) else {
return
}
let url = URL(string: "https://regexor.net/examples/google-sign-in-server-notification/")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.uploadTask(with: request, from: authData) { data, response, error in
print(response ?? ".")
// handle response from my backend.
if error != nil {
print("Error: \(String(describing: error))")
}
// Handle the response from the server
let dataString = String(data: data!, encoding: .utf8)
print ("got data: \(dataString!)")
}
task.resume()
}
}
Server script to get idToken form the native app:
In the example below we Just save the token to a file. In real life scenario, here we have to verify the identity of the id token before sending the access-token back to the app.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
// SAVE RAW DATA
$appleData = file_get_contents('php://input');
// Just saves the token to a file.
// In real life scenario, here we have to verify the identity of the id token before sending the access-token back to the app
$file = fopen("./data.txt", "a");
fwrite($file, $appleData);
fclose($file);
echo "send something back to the native app like acccess-token";
<?php
// SAVE RAW DATA
$appleData = file_get_contents('php://input');
// Just saves the token to a file.
// In real life scenario, here we have to verify the identity of the id token before sending the access-token back to the app
$file = fopen("./data.txt", "a");
fwrite($file, $appleData);
fclose($file);
echo "send something back to the native app like acccess-token";
<?php
// SAVE RAW DATA
$appleData = file_get_contents('php://input');
// Just saves the token to a file.
// In real life scenario, here we have to verify the identity of the id token before sending the access-token back to the app
$file = fopen("./data.txt", "a");
fwrite($file, $appleData);
fclose($file);
echo "send something back to the native app like acccess-token";