I’m having trouble finding out what went wrong while following along with chapter 24 and implementing Sign In with Apple for the web.
When I click the Sign In with Apple button and follow the steps, I get redirected to the route https://<my-ngrok-domain>/login/siwa/callback, but the response is 401 Unauthorized.
Here’s the code where the problem exist:
func appleAuthCallbackHandler(_ request: Request) throws -> EventLoopFuture<View> {
let siwaData = try request.content.decode(AppleAuthorizationResponse.self)
request.logger.info(Logger.Message(stringLiteral: request.cookies.all.description))
guard
let sessionState = request.cookies["SIWA_STATE"]?.string,
!sessionState.isEmpty,
sessionState == siwaData.state
else {
request.logger.warning("SIWA does not exist or does not match")
throw Abort(.unauthorized)
}
let context = SIWAHandleContext(
token: siwaData.idToken,
email: siwaData.user?.email,
firstName: siwaData.user?.name?.firstName,
lastName: siwaData.user?.name?.lastName
)
return request.view.render("siwaHandler", context)
}
The guard breaks and the log shows:
[ INFO ] [:] [request-id: 6C9456EE-4A7A-4988-B3B0-79507B797C96] (App/Controllers/WebsiteController.swift:345)
[ WARNING ] SIWA does not exist or does not match [request-id: 6C9456EE-4A7A-4988-B3B0-79507B797C96] (App/Controllers/WebsiteController.swift:352)
[ WARNING ] Abort.401: Unauthorized [request-id: 6C9456EE-4A7A-4988-B3B0-79507B797C96] (App/Controllers/WebsiteController.swift:353)
Although the browser clearly shows the SIWA_STATE cookie exist, the log shows that the cookies dictionary is empty. What could be the reason for this?
Thank you for replying, @0xtim. I added the breakpoint at the else block and found po request.cookies is still empty:
(lldb) po request.cookies
▿ HTTPCookies
- cookies : 0 elements
I’m guessing this is the reason why the else block is reached in the first place (because SIWA_STATE doesn’t exist at the point when the guard is reached). There is a value for siwaData.state at that point, but there is no value for SIWA_STATE to match against.
OK I discovered something that may be the cause. As soon as I click the Sign In with Apple button, the Chrome browser console prints the following error:
I’m guessing there’s something wrong with the way I wrote my script in register.leaf. I added this bellow as the book says:
When you say you’re following along with the chapter - are you following along exactly or are you trying to add your own stuff? I’m confused as to what app.js is? It looks like you’re setting a Content Security Policy unless Chrome is now doing that for you - is that right?
I created a vapor app since the start of the book and have been following along in the same app step-by-step. I didn’t encounter anything mentioning Content Security Policy and this is the first time I know about it.
It doesn’t work in Safari. Although I’m not sure the same error is printed in Safari’s console, I still get the 401 Unauthorized response at the end. I’m not currently on my Mac that has the Vapor app but I will check it later today and confirm if it’s the same error or not.
@0xtim I just checked in Safari and the console doesn’t show anything. However, the original problem is still there. I end up with a 401 Unauthorized error when reaching /login/siwa/callback.
Digging further, I add a breakpoint at the first line of appleAuthCallbackHandler(_:) to inspect the Request passed to that function.
I noticed that Request has a computed property called cookies under vapor/Sources/Vapor/Request/Request.swift defined as such:
/// Get and set `HTTPCookies` for this `HTTPRequest`
/// This accesses the `"Cookie"` header.
public var cookies: HTTPCookies {
get {
return self.headers.cookie ?? .init()
}
set {
self.headers.cookie = newValue
}
}
As you can see, the getter uses nil coalescing to return a new instance of Request if self.headers.cookie is nil. So the next thing I did was to po request.headers.cookie directly and indeed the value is nil:
(lldb) po request.headers.cookie
nil
The question is now why is this value nil?
PS To be 100% sure that my code isn’t the problem, I also tested the final version of the TIL app for chapter 24 from the book’s materials. The same problem is also there.
I added some parameters to the cookie, but it not working.
let cookie = HTTPCookies.Value(string: siwaContext.state, expires: expiryDate, maxAge: 300, domain: “apple.com”, path: “/login/siwa/callback”, isSecure: true, isHTTPOnly: true, sameSite: HTTPCookies.SameSitePolicy.none)
I tried to disable CSP in the browser, but it doesn’t help. Firefox: Type about:config in the Firefox address bar and find security.csp.enable and set it to false.
I suppose the problem in Vapor. Different requests don’t see cookies from each other.
Well, I commented the cookie check, I get empty email and name without any errors.
I’ve been looking in to this today and it’s a bug in Vapor. Basically a couple of months ago Vapor added support for better parsing of of HTTP headers to support complex accept headers (PR Here). That had the unintended side effect of breaking any headers with a date in, because valid cookie dates have a comma which we don’t currently account for. I’m working on a fix now.
Why is there a blank email address and name from apple, that doesn’t depend on the cookie?
P.S. Checking the cookie from a program request is successful. I don’t see a difference in the headers from a program request and the apple’s one.
It might be because the user has already signed in with the app? At which point you’re supposed to just look up the SIWA ID. I seem to remember that being a thing