Created
April 28, 2020 20:43
-
-
Save cardoso/de614f1b770a9404215eba93269fa559 to your computer and use it in GitHub Desktop.
Adding Sign in with Apple to your iOS App
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AuthenticationServices | |
class AuthViewController: UIViewController { | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setupSignInButton() | |
} | |
func setupSignInButton() { | |
let button = ASAuthorizationAppleIDButton() | |
button.center = view.center | |
view.addSubview(button) | |
button.addTarget(self, action: #selector(handleSignInPress), for: .touchUpInside) | |
} | |
@objc func handleSignInPress() { | |
performSegue(withIdentifier: "kAuthToContactsSegueId", | |
sender: nil) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ mkdir backend; cd backend | |
$ npm init --yes | |
$ npm install stream-chat apple-auth express node-persist jsonwebtoken --save | |
$ touch index.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const StreamChat = require('stream-chat').StreamChat; | |
const AppleAuth = requite('apple-auth'); | |
const express = require('express'); | |
const storage = require('node-persist'); | |
const jwt = require('jsonwebtoken'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const apiKey = '[api_key]' | |
const serverKey = '[server_key]' | |
const client = new StreamChat(apiKey, serverKey); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const appleAuth = new AppleAuth({ | |
client_id: "[client_id]", // eg: my.unique.bundle.id.iMessageClone | |
team_id: "[team_id]", // eg: FWD9Q5VYJ2 | |
key_id: "[key_id]", // eg: 8L3ZMA7M3V | |
scope: "name email" | |
}, './config/AuthKey.p8'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var app = express(); | |
app.use(express.json()); | |
storage.init(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
app.post('/authenticate', async (req, res) => { | |
const {appleUid, appleAuthCode, name} = req.body; | |
console.log(`[INFO] Sign in attempt with request: ${JSON.stringify(req.body)}`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let email; | |
try { | |
const response = await appleAuth.accessToken(appleAuthCode); | |
email = jwt.decode(response.id_token).email; | |
console.log(`[INFO] User identity confirmed by Apple.`) | |
} catch { | |
console.log(`[ERROR] Could not confirm user identity with Apple.`); | |
res.sendStatus(401); | |
return; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if(email && name) { | |
const streamId = Buffer.from(email).toString('base64').replace(/=/g, '@'); | |
const userData = {streamId, email, name} | |
await storage.set(appleUid, userData); | |
console.log(`[INFO] User registered with email: ${email}. Derived streamId: ${streamId}`) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const userData = await storage.get(appleUid); | |
if (!userData) { | |
console.log(`[ERROR] User not found in persistent storage.`); | |
res.sendStatus(404); | |
return; | |
} | |
const response = { | |
apiKey, | |
streamId: userData.streamId, | |
streamToken: streamClient.createToken(userData.streamId), | |
email: userData.email, | |
name: userData.name | |
} | |
console.log(`[INFO] User signed in successfully with response: ${JSON.stringify(response)}.`); | |
res.send(response); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const port = process.env.PORT || 4000; | |
app.listen(port); | |
console.log(`Running on port ${port}`); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ node index.js |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
struct AuthRequest: Codable { | |
let appleUid: String | |
let appleAuthCode: String | |
let name: String? | |
func encoded() -> Data { | |
try! JSONEncoder().encode(self) | |
} | |
} | |
struct AuthResponse: Codable { | |
let apiKey: String | |
let streamId: String | |
let streamToken: String | |
let email: String | |
let name: String? | |
init?(data: Data) { | |
guard let res = try? JSONDecoder().decode(AuthResponse.self, from: data) else { | |
return nil | |
} | |
self = res | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func authenticate(request: AuthRequest, | |
completion: @escaping (AuthResponse) -> Void) { | |
var urlReq = URLRequest(url: URL(string: "http://[your local ip]:4000/authenticate")!) | |
urlReq.httpBody = request.encoded() | |
urlReq.httpMethod = "POST" | |
urlReq.addValue("application/json", forHTTPHeaderField: "Content-Type") | |
urlReq.addValue("application/json", forHTTPHeaderField: "Accept") | |
URLSession.shared.dataTask(with: urlReq) { data, response, error in | |
completion(AuthResponse(data: data!)) | |
}.resume() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ipconfig getifaddr en0 | |
192.168.0.11 // example output |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@objc func handleSignInPress() { | |
let provider = ASAuthorizationAppleIDProvider() | |
let request = provider.createRequest() | |
request.requestedScopes = [.fullName, .email] | |
let controller = ASAuthorizationController(authorizationRequests: [request]) | |
controller.delegate = self | |
controller.presentationContextProvider = self | |
controller.performRequests() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension AuthViewController: ASAuthorizationControllerPresentationContextProviding { | |
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { | |
return self.view.window! | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension AuthViewController: ASAuthorizationControllerDelegate { | |
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { | |
let cred = authorization.credential as! ASAuthorizationAppleIDCredential | |
let code = String(data: cred.authorizationCode!, encoding: .utf8)! | |
var name: String? = nil | |
if let fullName = cred.fullName { | |
name = PersonNameComponentsFormatter().string(from: fullName) | |
} | |
let request = AuthRequest(appleUid: cred.user, appleAuthCode: code, name: name) | |
authenticate(request: request) { [weak self] res, error in | |
DispatchQueue.main.async { | |
guard let res = res else { | |
let alert = UIAlertController(title: "Error", message: error, preferredStyle: .alert) | |
alert.addAction(UIAlertAction(title: "OK", style: .default)) | |
self?.present(alert, animated: true) | |
return | |
} | |
Client.config = .init(apiKey: res.apiKey, logOptions: .info) | |
let extraData = UserExtraData(name: res.name) | |
let user = User(id: res.streamId, extraData: extraData) | |
Client.shared.set(user: user, token: res.streamToken) | |
self?.performSegue(withIdentifier: "kAuthToContactsSegueId", sender: nil) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[INFO] Sign in attempt with request: {"name":"Matheus Cardoso","appleUid":"001482.30f24b627a403ee4837b27a403ee6a22.1758","appleAuthCode":"ce73969641ba34969a5e69641ba349697.0.nruys.ElBakUhUlBakUhUZMB-xJQ"} | |
[INFO] User identity confirmed by Apple. | |
[INFO] User registered with email: [email protected]. Derived streamId: bWF0aGV1c0BjYXJkby5zbw@@ | |
[INFO] User signed in successfully with response: {"apiKey":"zgtb7uugmvx3","streamId":"bWF0aGV1c0BjYXJkby5zbw@@","streamToken":"eyJhbGciOiJ5cCI6IkpXVInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiY1c2VyX2lkFjMEJqWVhKa2J5NXpid0BAIn0.1FqhMbQU70EB-i837w7oKcWLeon2FqhMbQU707cdSP8","email":"[email protected]","name":"Matheus Cardoso"}. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment