Closed LukasKnuth closed 1 year ago
Hey. So for apple login, apple should redirect the user to the {apiDomain}/auth/callback/apple
with the code and state.
This API, further redirects the user to the websiteDomain/auth/callback/apple
with the same code and state. On this page, our prebuilt UI checks for the state and calls the signinup API using the code sent by apple (just like how something like google sign in works). This API then exchanges the code + secret to get the id token on the backend which then gives the user info and creates a user in supertokens.
Currently, we're not using the frontend at all, since we're building the logins for Apps only.
What would the App be expected to call? I assume it's auth/signinup
with the code
from the apple redirect? And if so, why does neither code
nor id_token
work for that API?
I feel like I'm missing a step here.
This API then exchanges the code + secret to get the id token on the backend
What API on the backend does that?
Right. I thought you were making for webapp.
So for mobile apps, you can see this guide here: https://supertokens.com/docs/thirdparty/custom-ui/thirdparty-login (click on the mobile code tab), and you need to pass the code
to the signinup API just like the curl command shown in the docs.
You can also see how we do it for react native app here: https://github.com/supertokens/supertokens-react-native/blob/master/examples/with-thirdpartyemailpassword/apple.js#L48.
This doesn't seem to be a bug in our sdk, so i am closing this issue, but feel free to continue the conversation in the closed issue or as on our discord.
So, I have started debugging the application locally because when I send the code
received from Apple to /signinup
, I get the following stack trace:
JWSInvalid: Compact JWS must be a string or Uint8Array
at compactVerify (/Users/lukasknuth/dev/identity/node_modules/jose/dist/node/cjs/jws/compact/verify.js:12:15)
at Object.jwtVerify (/Users/lukasknuth/dev/identity/node_modules/jose/dist/node/cjs/jwt/verify.js:9:58)
at Object.<anonymous> (/Users/lukasknuth/dev/identity/node_modules/supertokens-node/lib/build/recipe/thirdparty/providers/utils.js:74:40)
at Generator.next (<anonymous>)
at /Users/lukasknuth/dev/identity/node_modules/supertokens-node/lib/build/recipe/thirdparty/providers/utils.js:66:75
at new Promise (<anonymous>)
at __awaiter (/Users/lukasknuth/dev/identity/node_modules/supertokens-node/lib/build/recipe/thirdparty/providers/utils.js:48:16)
at Object.verifyIdTokenFromJWKSEndpoint (/Users/lukasknuth/dev/identity/node_modules/supertokens-node/lib/build/recipe/thirdparty/providers/utils.js:73:12)
at Object.<anonymous> (/Users/lukasknuth/dev/identity/node_modules/supertokens-node/lib/build/recipe/thirdparty/providers/apple.js:159:51)
at Generator.next (<anonymous>)
While debugging, I noticed that this is simply a follow-up error. It happens in https://github.com/supertokens/supertokens-node/blob/0faebfae435fd661f4b6657e2ca510101da012f5/lib/ts/recipe/thirdparty/api/implementation.ts#L128-L136 when the response is not successful.
In my case, it returns the following body:
{
error: "invalid_grant",
error_description: "redirect_uri mismatch. The code was not issued to https://<my-domain>/auth/callback/apple.",
}
After the code
from apple is expired (after 5min) it returns:
{
error: "invalid_grant",
error_description: "The code has expired or has been revoked.",
}
Both of these legitimate error responses cause the same stack trace and HTTP 500 response, which obscures the actual underlying problem.
This is because the code assumes the response is always successful and accesses accessTokenAPIResponse.id_token
in https://github.com/supertokens/supertokens-node/blob/0faebfae435fd661f4b6657e2ca510101da012f5/lib/ts/recipe/thirdparty/providers/apple.ts#L118-L121 which in case of an error is simply undefined
, causing the crash and stack trace.
So the issue seems to be that I don't have a correct redirect URI registered with apple.
The SDK assumes the redirect URI that is configured with Apple is always the /callback/apple
endpoint provided by SuperTokens: https://github.com/supertokens/supertokens-node/blob/0faebfae435fd661f4b6657e2ca510101da012f5/lib/ts/recipe/thirdparty/providers/apple.ts#L139-L146
As far as I understand, this isn't required when implementing the flow for iOS Apps which can use the official Apple SDK. The problem is that if I configure a different callback URL in Apples Developer Console and pass it to /signinup
, it's ignored and SuperTokens will still always use it's own callback URL when calling https://appleid.apple.com/auth/token
to swap the code
for an auth token with the Apple Server: https://github.com/supertokens/supertokens-node/blob/0faebfae435fd661f4b6657e2ca510101da012f5/lib/ts/recipe/thirdparty/providers/apple.ts#L71-L77
This API call verifies that the redirect_uri
parameter it receives is the same that is set in the code
. If it doesn't match, the above error is returned. AFAIK, it's not documented that one must use the SuperTokens callback.
So with the redirect URI fixed and pointing to where SuperTokens expects, I give the resulting code
to /signinup
and get the following response from the Backend SDK:
Error: SuperTokens core threw an error for a POST request to path: '/recipe/signinup' with status code: 400 and message: Field name 'id' is invalid in JSON input
at Querier.<anonymous> (/app/node_modules/supertokens-node/lib/build/querier.js:310:31)
at Generator.next (<anonymous>)
at fulfilled (/app/node_modules/supertokens-node/lib/build/querier.js:51:36)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
This is the stack-trace logged by SuperTokens Core:
ERROR | pid: 62c11da4-8a2d-44f9-82b6-2dff3ef8ac38 | [http-nio-0.0.0.0-3567-exec-5] thread | io.supertokens.webserver.WebserverAPI.service(WebserverAPI.java:174) | jakarta.servlet.ServletException: io.supertokens.webserver.WebserverAPI$BadRequestException: Field name 'id' is invalid in JSON input
at io.supertokens.webserver.InputParser.parseStringOrThrowError(InputParser.java:142)
at io.supertokens.webserver.api.thirdparty.SignInUpAPI.doPost(SignInUpAPI.java:93)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)
at io.supertokens.webserver.WebserverAPI.service(WebserverAPI.java:172)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:832)
Caused by: io.supertokens.webserver.WebserverAPI$BadRequestException: Field name 'id' is invalid in JSON input
... 23 more
Let me re-state what I'm trying to do: Setup "Sign in with Apple" for an Android App where (as I understand) the same flow as for Web is used.
supertokens-node
: 14.1.3supertokens-core
: 5.0.0Looking deeper into this, I can now see that the object that the backend SDK sends to SuperTokens Core looks as follows:
{
"thirdPartyId": "...",
"thirdPartyUserId": "...",
"email": { "id": null }
}
It makes sense that this would cause an error in the core, although the Error message it returns makes it seem like and id
property is not expected or allowed in the request, but it's just not allowed to be null.
This happens because https://github.com/supertokens/supertokens-node/blob/0faebfae435fd661f4b6657e2ca510101da012f5/lib/ts/recipe/thirdparty/providers/apple.ts#L125-L137 attempts to extract email
and email_verified
properties from the id_token
returned by Apple.
The id_token in my case does not have that information in its payload:
{
"iss": "https://appleid.apple.com",
"aud": "<redacted>",
"exp": 1689664380,
"iat": 1689577980,
"sub": "<redacted>",
"at_hash": "sqt3b7LXLshzCmf7wYiGIg",
"auth_time": 1689577942,
"nonce_supported": true
}
The URL I'm using to request the code
(which is then exchanged for the token) includes scope=name%20email
, so I am requesting the email scope specifically.
I have it working!
The issue was that the user I was using to test the login was in a bad state for what I was trying to do. Initially, I didn't set the scope
query parameter in my request to apple. This caused the first Sign Up with Apple prompt to not include the email
scope. Subsequent request, even with scope=email
didn't add the email and also didn't re-prompt me to share my email either.
I fixed it by going to https://appleid.apple.com/account/manage, clicking on "Sign In with Apple" -> My application -> "Stop using Sign In with Apple".
After this, I did the same request to Apple again but this time I was asked to provide either my email or the privacy setting. And now, my id_token
has the expected email
and email_verified
fields and the request goes through and the SuperTokens user is created!
I haven't found any documentation on how to setup specific providers, so I have somewhat guessed my way to where I am now.
Using
code
I use
https://appleid.apple.com/auth/authorize?response_type=code id_token&redirect_uri=<redirect-url>&client_id=<client-id>&response_mode=form_post
from the Apple DocumentationI assume the callback URL must be
/auth/callback/apple
from the FDI documentation so I have registered that with Apple.As far as I can tell the
code
I get back is useless and only theid_token
is required. When the Apple page redirects me to the apple callback, it immediately redirects me to my API domain withstate
andcode
in the query parameters.The
code
is the code from the apple response and doesn't work with/auth/signinup
, I get the following Stack Trace in the response:Request:
Response
Using
id_token
When I open the "Network inspector" in FireFox on the Apple Page I can see that the POST request it makes to my callback-URL contains the
id_token
as well. That token is lost when the callback redirects me again (as described above).If I use this token as the
access_token
I get the same error as above. However, reading the source of the Apple provider:https://github.com/supertokens/supertokens-node/blob/b3e1c19c431bd70de6a2e365a1e2117241a67fbb/lib/ts/recipe/thirdparty/providers/apple.ts#L106-L139
The
access_token
is never used in that function, instead onlyid_token
is used. Providing this in the request gives another error:Request
Response
I assume this is a static validation rule, because if I specify anything for
access_token
and also specify theid_token
, I get another error:Request
Response
This is where I currently am stuck. I feel like I'm probably misunderstanding something fundamentally here, all the other Providers I have used before where very straight forward.
Can you help me get this setup?