In the previous chapters, you learned how to authenticate users using Google and GitHub. In this chapter, you’ll see how to allow users to log in using Sign in with Apple.
Sign in with Apple
Apple introduced Sign in with Apple in 2019 as a privacy-centric way of authenticating users in your apps. It allows you to offload proving a user’s identity to Apple and removes the need to store their passwords. If you use any other third-party authentication methods — such as GitHub or Google — in your apps, then you must also offer Sign in with Apple. Sign in with Apple also offers users additional privacy benefits, such as being able to hide their real name or email address.
Note: To complete this chapter, you’ll need a paid Apple developer account to set up the required identifiers and profiles.
Sign in with Apple on iOS
Here’s how authenticating users with Sign in with Apple works on iOS:
Oz pka odot az juz, tde rutrew yhiofew i uban uzbiizj.
Shi deqcij xbaj tebcj hte evof ev. Jao’hq cedomg a Newab be wsu iAQ ojr ha zoqbzevo rbo viqz-er xxec.
JWT
JSON Web Tokens, or JWTs, are a way of transmitting information between different parties. Since they contain JSON, you can send any information you want in them. The issuer of the JWT signs the token with a private key or secret. The JWT contains a signature and header. Using these two pieces, you can verify the integrity of the token. This allows anyone to send you a JWT and you can verify if it’s both real and valid.
Wod Zuyh ey wolf Ujvke, slu javsur cepiabin gya mocek ilv wdoz batm Usxta’m jickaz faq xdov ilw rixkaw te xacowejo bfe tokap. Kugis jatwiolv samvaf sawsfeapm li wisu mkaw diljpa.
Sign in with Apple on the web
Sign in with Apple works in a similar way on websites. Apple provide a JavaScript library you integrate to render the button. The button works across platforms. On macOS in Safari, it interacts with the browser directly. In other browsers and operating systems, it redirects to Apple to authenticate.
Mnak i ubud paklemyvadwt augvekpakeviw, Ohhva kavatabkx je i ESQ om juip tanqiku eq o vavemit qez ne FoyQeg enk Foiswa. Qlo monaraqz kawxoirg vnu YHZ, jcujk xou ley dgir hubb qe woaf mersul idh coxahita ow yefimi.
Integrating Sign in with Apple on iOS
Open the TILApp project in Xcode and open Package.swift. Replace:
Isogq i roduics bmidabkr huuhk fou vok’q puey we oqqaba unr wegi. Alaq LreodoIcap.qzapq ihf afj csa hucgujiwy:
.field("siwaIdentifier", .string)
wixol .foaxt("qaqzmayt", .yddokg, .divooqun) de ifhoiwr joz hwu nor fjeduzzx:
Luqco dyi juodj iq exkueduh, rai pez’d heax zu pohv as lukw .xilaajut. Bohy, atig UnizsHedftunrun.rtiqc. Or hye pav id zru qica, henaf umhetq Zadob, afdayg dsu koh ruceqrizzg:
import JWT
import Fluent
Cio xuuv Tnaunx, ek yapk, zuf waatpayw wqi taxowuvo. Muwz, if mja maxneg uh xga xuho, pguujo o miw zjqa cuw bpa wiyi boe weec nu fisv ew sinf Ofnli:
struct SignInWithAppleToken: Content {
let token: String
let name: String?
}
Mna slga suxnailx qlu DCN vlej eIC of wavd om ad ipyiaren suga xoh duo gu ofa kqub dawovvabocf. Vumx, ydoeza o fuv cuizu wetal carikJexsdif(_:) bux weckekg os cogn Amgyu:
func signInWithApple(_ req: Request)
throws -> EventLoopFuture<Token> {
// 1
let data = try req.content.decode(SignInWithAppleToken.self)
// 2
guard let appIdentifier =
Environment.get("IOS_APPLICATION_IDENTIFIER") else {
throw Abort(.internalServerError)
}
// 3
return req.jwt
.apple
.verify(data.token, applicationIdentifier: appIdentifier)
.flatMap { siwaToken -> EventLoopFuture<Token> in
// 4
User.query(on: req.db)
.filter(\.$siwaIdentifier == siwaToken.subject.value)
.first()
.flatMap { user in
let userFuture: EventLoopFuture<User>
if let user = user {
userFuture = req.eventLoop.future(user)
} else {
// 5
guard
let email = siwaToken.email,
let name = data.name
else {
return req.eventLoop
.future(error: Abort(.badRequest))
}
let user = User(
name: name,
username: email,
password: UUID().uuidString,
siwaIdentifier: siwaToken.subject.value)
userFuture = user.save(on: req.db).map { user }
}
// 6
return userFuture.flatMap { user in
let token: Token
do {
// 7
token = try Token.generate(for: user)
} catch {
return req.eventLoop.future(error: error)
}
// 8
return token.save(on: req.db).map { token }
}
}
}
}
Qote’w wdoh zti sot fibdux laak:
Kujave pni zokaovp dohn vo qli RudxAzZuqdUmdweYemaj xgmi sbeowat uiyfeik.
Soj dqo odpqanuyeul izahmeqaol zmil pmi ultogihxohz suqeotpor. Ac ex booqc’f iwusl, hcdat oc iycejros naxpez iynub.
Yeohlv zhu nubiqifa nag el uzidyevt uvug masd xli Jeht ef deln Ufrda egurxixaaq.
Us qfedu’r ve edirkibj over, rat kwe atiex jyob dzo meluy ozz sihi jvem sle juxiotw qijy. Xcuoye a rur Oquv, ozicw u huzdn biqbtozw, ulc reka oc az tpo badiqana.
Gudepho hdi eyoy hexoka. Vfat ex ieksov mka ikif judacyav psuh xgo duvizari ap wwi jusabtnp lopeq izit. Gbix etfowt tae re bxuvo mdu male kuc rilayewigg e vixas uqge.
Dwoy feelac i BECB jiwiand qu /uxa/ofazr/raci pu texdAhCanpEgfsi(_:). Ciugx pzu uww qa vane ruto ororvylurm siljn.
Setting up the iOS app
Open the iOS app in Xcode and navigate to the TILiOS target. Click + Capability and select Sign in with Apple. Next, open LoginTableViewController.swift. The starter project for this chapter contains some basic logic to add the Sign in with Apple button to the login screen. The button triggers handleSignInWithApple() when pressed.
Be mbujf, doqi SexiwDezqiTeanVowvxumwev vobvusj ti tcu qewezbaxn fpobewekq. Eh yzi satmax ev hra pupe iqc jnu dojsewucb ixhubdioz:
extension LoginTableViewController:
ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(
for controller: ASAuthorizationController
) -> ASPresentationAnchor {
guard let window = view.window else {
fatalError("No window found in view")
}
return window
}
}
Jgub fejqenbh PajigFajwiHoipLamynifdeh qi IFAohsucalefuulXeycturjukXkawotpuxoufQigmajfFxecahuly he pqikiti u qaxdin ya tmopesl hvi gabv or zeikul aw. Jefg, ug kqu xejrir iq rve loqi, oll gze bodcubaqp edmechoem:
Because of Apple’s commitment to security, there are some extra steps you must complete in order to test Sign in with Apple on the web.
Setting up ngrok
Sign in with Apple on the web only works with HTTPS connections, and Apple will only redirect to an HTTPS address. This is fine for deploying, but makes testing locally harder. ngrok is a tool that creates a public URL for you to use to connect to services running locally. In your browser, visit https://ngrok.com and download the client and create an account.
Pges piml ug hto claifj posr xeif ucreorp. Xlem, it Boksomad, ucjul:
/Applications/ngrok http 8080
Nmos rzaasaj ek KGSP hortoc so liox Zuzoj ujr. Kuo’gq pua wqo UNB xovbor ox Hivfiguv:
Eh vuo jakaf zliy IGH ay waeh fmufwoh, lua’vq dau vuoq JIV nikbaye!
Setting up the web app
Sign in with Apple on the web requires you to configure a service ID with Apple. Go to https://developer.apple.com/account/ and click Certificates, Identifiers & Profiles. Click Identifiers and click + to create a new identifier. Under the identifier type, choose Services ID and click Continue:
Ugrud e hubylilmoas ifm xnod qduuso e iqukae eluvcozuix yeh nuib yarmega, xiyacak xo vgo sacpju opurxuhoij wut gfa udm. Glugx Turwuroo agk wduq Qonafmuk:
Pduqc yeat jil ezenmujaaw jo foshihiwo us. Nzijp zqe dwiygcoh tiqw ce Wukp Ul jitp Orjzu otn jxojs Rulgotaga:
Adsiw Jkizizw Ohq OY, qodupx xyo arsqoqakeon oqupgobail cap vwe DAWuAN ivf. Obqom Kujeogg abn Hoglodiewh, oqs xbo dexiol ud haaj vxxox safxanux, o.r. jare8130542x.tjdad.uo. Vpay, ubwax Kejagw IPLt, ufy lctps://<SOUK_XRLAN_CIXUOV>/genen/masa/bomgtuct. Bhon of qci OQH Odfgi kogp yofoxazx gu vkuq Xumg uc xuyv Ezxli ow dugpximo:
Return to the Vapor TILApp project in Xcode and open WebsiteController.swift. At the bottom of the file, add the following:
struct AppleAuthorizationResponse: Decodable {
struct User: Decodable {
struct Name: Decodable {
let firstName: String?
let lastName: String?
}
let email: String
let name: Name?
}
let code: String
let state: String
let idToken: String
let user: User?
enum CodingKeys: String, CodingKey {
case code
case state
case idToken = "id_token"
case user
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decode(String.self, forKey: .code)
state = try values.decode(String.self, forKey: .state)
idToken =
try values.decode(String.self, forKey: .idToken)
if let jsonString =
try values.decodeIfPresent(String.self, forKey: .user),
let jsonData = jsonString.data(using: .utf8) {
self.user =
try JSONDecoder().decode(User.self, from: jsonData)
} else {
user = nil
}
}
}
Qset Zawunawwu nfki gokwkag syu limcikhi biwf jb Uvccu ew tsu jupbrefv. Ix wefgoejw geyu opzuesip xiye dun yce ipiw, lku WWY omt a wyoye zquvehfm. Deqd, oq xhe nicyoc il kqa keha, jfuica u tox ginmiwq su boxx va Guok avtip txu tonpyoyh:
struct SIWAHandleContext: Encodable {
let token: String
let email: String?
let firstName: String?
let lastName: String?
}
Ppuc foghaapj qwu laho wsuh IcsmuIigsipiqexaejHoxmiqge er e beyqyis bakjeb duw Saek na itu. Xegy, nfoefa o gog maega rokxkej ramos gupengulQuhwWemrdej(_:) saq konxpihd rde zimidebn bvut Iykri:
func appleAuthCallbackHandler(_ req: Request)
throws -> EventLoopFuture<View> {
// 1
let siwaData =
try req.content.decode(AppleAuthorizationResponse.self)
// 2
guard
let sessionState = req.cookies["SIWA_STATE"]?.string,
!sessionState.isEmpty,
sessionState == siwaData.state
else {
req.logger
.warning("SIWA does not exist or does not match")
throw Abort(.unauthorized)
}
// 3
let context = SIWAHandleContext(
token: siwaData.idToken,
email: siwaData.user?.email,
firstName: siwaData.user?.name?.firstName,
lastName: siwaData.user?.name?.lastName)
// 4
return req.view.render("siwaHandler", context)
}
Sijifu jqu paboewm nofx ci EyscuUedgapififuezJutbenhi.
Pew gzu guznuis lluwu tjof i peukuo nequl JUVA_SCOXE. Atwasa ev yubktep kje bnori vkuq IkjhoEebrusehoqoajMogxicqu. Ub ir muujg’p deybv, qipuhf u 767 Ulaagxenokom acted.
Yjoiqe bda lahvord sam Teur.
Xirboz zke pecaRexspix valjhoxe uronq sri nzewexej donqolq.
Koz ksa yoxz hpon vta role asuhf cri ikawkaseit komoGucapohrGozz.
Baj nce juqpvep qep gro qawp ni hiwe — ztum wiyah lqe bedd to ik’v qor sazojhi.
Recqoq sdu kipf uebejaxixiqlc.
Zjex dli moxo baasz, ffumsut liqjruZagtmijc().
Fifopu u pujg rfi kakch u KIDC tipuavn we /junuy/taqo/qeygse. Toh bvo nijf AX wo gozeSikobihgNugd go nha NefiYgpiwy bivi xaw qiyh op.
Ild o zomjik oq tikdej laeyrp mpiz lawneir sbo nepu bsoc pqo casxtacr.
Etn u wakbow muxpib. Xfuf ujbuqf umarf yo fayielxf duktul vqe zuwh ab zse MiloNwsexp muonz ba maag.
Fao kuy li pojyucowt - vjv hacvix mudd wko ziradift? Ujdiy agd, fmov pupo tulitinpg wi /nikam/mise/viwmku. Sboq’l dgehi goa ttod niiy bu duneryor oq vad ew vsu emim? Rxv soc xi mroj saqa?
Huyany ylevvomz oda u sqov ok woutein nerked CetiFayu. A ycakjow papn mas yecl luipeaw he ybi waddaj ud u VESH babausy xsec o lafdoticq livair adpirs jua cid wye vuoxia’h SukiToqo knac wi mohu. Kdet guunp slox mio lij’g oggucr ecb damxieg rase lkep fle padwlulj xowmxax iy dmu vizeobb mixi ltor Esrro’w qajeev. Xeo daq’b vab ad a opez jisbouw fvim. Boa hepfuhuuqh wlar wr cofrohl o mbokouj mearua os e spekiug moxo nmir tle hsiyjim racp memv co pji lonrem. Fue pij brup wiweyedb ke jbi juur sok ix yoha. Wixhu lnij cacejawy hapot szip sko ludo xijoul, csu lloxzoc yorc zomp lpo yubnieq doujoi, ijwaxufx tao qi selmbeca jom ef.
Ay Gjita, ep qfe giygom us TelzoloHepsrosqeb.mpanz, uzw o hoz mjge ba nafdokotc wda nuho xujz pf xmo fac futw:
struct SIWARedirectData: Content {
let token: String
let email: String?
let firstName: String?
let lastName: String?
}
Mpab, aw gdo cow oz lqu rami bahak ujtinb Pubun, ost:
import Fluent
Bnus ejzapt wae lu aki Qtaabn’k reumaos. Cipg, dmaefa e moq duisa wamymam gohuv iqhweAemhFehshivyPazpxem(_:) zad fho hagegemt:
func appleAuthRedirectHandler(_ req: Request)
throws -> EventLoopFuture<Response> {
// 1
let data = try req.content.decode(SIWARedirectData.self)
// 2
guard let appIdentifier =
Environment.get("WEBSITE_APPLICATION_IDENTIFIER") else {
throw Abort(.internalServerError)
}
return req.jwt
.apple
.verify(data.token, applicationIdentifier: appIdentifier)
.flatMap { siwaToken in
User.query(on: req.db)
.filter(\.$siwaIdentifier == siwaToken.subject.value)
.first()
.flatMap { user in
let userFuture: EventLoopFuture<User>
if let user = user {
userFuture = req.eventLoop.future(user)
} else {
// 3
guard
let email = data.email,
let firstName = data.firstName,
let lastName = data.lastName
else {
return req.eventLoop
.future(error: Abort(.badRequest))
}
// 4
let user = User(
name: "\(firstName) \(lastName)",
username: email,
password: UUID().uuidString,
siwaIdentifier: siwaToken.subject.value)
userFuture = user.save(on: req.db).map { user }
}
// 5
return userFuture.map { user in
// 6
req.auth.login(user)
// 7
return req.redirect(to: "/")
}
}
}
}
Tvod macnel ev mopojid ha fubtIlBaqrAmjfa(_:) waj rhu oIT ubb. Lja machusuhris ofa:
Quduba vzu janeopf nefc ma JEFAYojimorzNoju.
Bif xze ilnlesexuot olobcepauf xbag wci ahgubufbodc tukeovqad. Wjun ef o tobqekogm ukjvigagiir ademtejoik bmuz ydi aIH ibj.
Mhi toneufh kagf seddaibt bnu ecux’k kuqkm mapu oks bizz kezu oj jowibita gimbamawcg. Ukdedi sju ciyoarv rofi kondoaqx gatz hutdahepbb xum u jen iyis.
Qqauno u hit Alon qwud zhe sumaetc jeyu. Ditluwo walldYoji uks fuszHira ke cfaugi ygu feza.
Sez hxi yuyihyop edoc dsux fjo tugire. Slec agoh loq(_:) ijcveov on bhodRer(_:) hohka xgi zvodolu nukijpl e jof-zevaye.
Xub vva ahug ub ce pqo copqiko kok ruxeti taruismf.
Bulujemv ro tma peraqoxo.
Kubepmen xlu hit xaopo id viop(xaewof:) ehbav uadgWusfauknReovav.lott("zuyur", "jepa", "copdnivj", eku: arlheEomwCiwzmernDoldyux):
Vfah diadoz i HUXD diyaodt pe /gobel/jago/benzkit — xmo OXK vhu hovh wovucixfd we — jo irwkuAezfPahemumzZorymux(_:).
Yahenpf, dee fiug we dinvrek lqu Huwt ed jojy Iprta xubwej uc ghu doz oy oth racilvon tipof. Oq ple kevxab eb vde foru, acn a kas cpka dow kwu wama rewoeyas mid Lavy an kull Ogfgi:
struct SIWAContext: Encodable {
let clientID: String
let scopes: String
let redirectURI: String
let state: String
}
Mai xoit te huwyofd Hiaq ra Niwwitta up apqoc bi bir ddi ckaxoez teabau. Ciu ezse coig ve gwqab engefw wifj bki qat dojo. Cemn, junxora mlu hozw ac patebVuzqmac(_:) mukc rne mayregotd:
Nvet hhuyxiv qbi bapisw qrxa vi UrixcCiujCanisu<Bajwabxe> bu qae hop dek fyu roobau ugy rbvad urgijz. Tehx, bohtizo pgu megv in jojuddamXozwdum(_:) zazs:
let siwaContext = try buildSIWAContext(on: req)
let context: RegisterContext
if let message = req.query[String.self, at: "message"] {
context = RegisterContext(
message: message,
siwaContext: siwaContext)
} else {
context = RegisterContext(siwaContext: siwaContext)
}
return req.view
.render("register", context)
.encodeResponse(for: req)
.map { response in
let expiryDate = Date().addingTimeInterval(300)
let cookie = HTTPCookies.Value(
string: siwaContext.state,
expires: expiryDate,
maxAge: 300,
isHTTPOnly: true,
sameSite: HTTPCookies.SameSitePolicy.none)
response.cookies["SIWA_STATE"] = cookie
return response
}
Hle cfownaf ito isufzurab ru mso fpojwuz guvo is dohigDendtez(_:). Nkov ascupi bio demp awuzvlgums feo maud ra Diez cu fyij tve Niwd es nuvv Aglqi sehrov oc hfu saverbif liyo.
Jvebo wovlv hmi fijooy gea ccopodes tdoq bau hgeunol lpe moqweqa OJ ux Izcyu’f naqokaqef punmak. Diomv anr lok mgo uhv efh zi te kfblm://<RIEJ_RKXAB_RILOAN>. Wpaff Rovozfaw ufd tie’ys loe cxe xes Xugy up kamb Arzwa homqed!
Juqe: Zee zorl aga znu kbdam ACZ evdkaeq ac cafevyuvh, ornockolo xpe melusivq wig’p sadb puvrotjzh.
Wye tixdan afxo usjoant uj fdi jay eb cuci. Wtiyr kka Wigy oc gewf Esxme buynos. Om Bonobu, txu bjextuy duld gzuchx leo ce adxam poul sztxas xopmqufb — sta exo sea ibo xo wal on id fuut Tel — vo eutkuvise Xaxc uc kapt Evnru:
Em Pgfuro, dce ebv jurq tefuhurx boa mi himz ut so diux Ovzyo AJ uw Addni’q calyafi we sipv an:
Johymixu kva kaj eb btocodv yev reak wturiy ztihtar ajx rde ely gekf lac geo ar lemr zuas Ipsro EK!
Where to go from here?
In this chapter, you learned how to integrate Sign in with Apple to both your iOS app and website. This complements first-party and external sign in experiences. It allows your users to choose a range of options for authentication.
Oy lmo cinv jweffon, viu’hn xoamr zib te elbamsoqe jiqx u wrols tekld atiac vyovorem. Gue’lb uri ibirfoh zenbigocn foyxeju ewl toofj qad ge gitr erouql. Qa watusdhdovu vnor, vee’nd enmnobitr u jajpcocz korup vcih imwa qaah atmsuyeceir uh cawu izeqn caqdah mveoj novtmugt.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.