Skip to content

Instantly share code, notes, and snippets.

@cardoso
Created April 28, 2020 20:43
Show Gist options
  • Save cardoso/de614f1b770a9404215eba93269fa559 to your computer and use it in GitHub Desktop.
Save cardoso/de614f1b770a9404215eba93269fa559 to your computer and use it in GitHub Desktop.
Adding Sign in with Apple to your iOS App
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)
}
}
$ mkdir backend; cd backend
$ npm init --yes
$ npm install stream-chat apple-auth express node-persist jsonwebtoken --save
$ touch index.js
const StreamChat = require('stream-chat').StreamChat;
const AppleAuth = requite('apple-auth');
const express = require('express');
const storage = require('node-persist');
const jwt = require('jsonwebtoken');
const apiKey = '[api_key]'
const serverKey = '[server_key]'
const client = new StreamChat(apiKey, serverKey);
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');
var app = express();
app.use(express.json());
storage.init();
app.post('/authenticate', async (req, res) => {
const {appleUid, appleAuthCode, name} = req.body;
console.log(`[INFO] Sign in attempt with request: ${JSON.stringify(req.body)}`);
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;
}
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}`)
}
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);
});
const port = process.env.PORT || 4000;
app.listen(port);
console.log(`Running on port ${port}`);
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
}
}
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()
}
$ ipconfig getifaddr en0
192.168.0.11 // example output
@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()
}
extension AuthViewController: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
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)
}
}
}
}
[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