Apple provider broken - blowing up when not finding userinfo endpoint
Provider type
Apple
Environment
System: OS: Linux 5.19 KDE neon 5.27 5.27 CPU: (16) x64 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz Memory: 6.74 GB / 15.34 GB Container: Yes Shell: 5.1.16 - /bin/bash Binaries: Node: 16.18.0 - ~/apps/node-v16.18.0-linux-x64/bin/node npm: 9.3.0 - ~/apps/node-v16.18.0-linux-x64/bin/npm Browsers: Brave Browser: 110.1.48.164 Chrome: 110.0.5481.100 Firefox: 110.0
Reproduction URL
Needed?
Describe the issue
Seems as if Apple another one of those providers that does not provide user info.
When the redirect endpoint is invoked I get the following error:
[auth][cause]: TypeError: TODO: Authorization server did not provide a userinfo endpoint.
at handleOAuth (file:///xxx/node_modules/@auth/sveltekit/node_modules/@auth/core/lib/oauth/callback.js:28:19)
...
The line in question is here.
I found a relevant issue that had a specific work-around, but pertained to Todoist.
Doesn't seem to me that requiring a user-info endpoint is part of the OAuth spec, but I could be wrong. I would rather have this restriction be a configuration option or handled explicitly by some callback. I wouldn't be surprised if the push for privacy is going to push additional partners to follow a similar pattern.
How to reproduce
Try to do a sign-in using Apple.
Expected behavior
For it to work and have the client specify how to handle the missing information.
I'm running into this too. @kyllerss did you find a workaround?
I've put this task aside for now, so I haven't had a chance to do a workaround.
@kyllerss @jschlesser The debug message here is a bit unclear. If you provide a token endpoint, a userinfo endpoint is not necessary.
Additionally the authorization endpoint returns the relevant data only in the body of the response, instead of the query, like Auth.js would expect it. Therefor you have to use response_mode: 'query' for the authorization config. This comes with the downside, that you cannot request the email, or name of the user. I'm currently working on a fix to this.
For now, if you still want to use Sign in with Apple using Auth.js you can use this Apple provider configuration example to get it working:
Apple({
clientId: APPLE_ID,
clientSecret: APPLE_SECRET,
wellKnown: "https://appleid.apple.com/.well-known/openid-configuration",
checks: ["pkce"],
token: {
url: `https://appleid.apple.com/auth/token`,
},
authorization: {
url: 'https://appleid.apple.com/auth/authorize',
params: {
scope: '',
response_type: 'code',
response_mode: 'query',
state: crypto.randomUUID()
},
},
client: {
token_endpoint_auth_method: "client_secret_post",
},
})
In addition you'll need to set the secret for jwt encoding and the pkceCodeVerifier cookie on the top level of the auth configuration:
cookies: {
pkceCodeVerifier: {
name: "next-auth.pkce.code_verifier",
options: {
httpOnly: true,
sameSite: "none",
path: "/",
secure: true,
},
},
},
secret: AUTH_SECRET
I hope this helps :)
Any news on this? @ChrGrb configuration doesn't work for me :(
@ChrGrb it sort of works, but it doesn't return email/name and setting authorization.params.scope to something other then "" gets other errors.
There is no point using it if you get this:
so do I understand that it is impossible to get the email and name from apple sign in?
Any updates? The same issue still occurs with version 5.0.0-beta.16. @ChrGrb method enables Apple login but cannot get the name.
How can I get other values defined in the name or scope?
The problem seems to be quite old. Are there any plans to resolve this issue? I like using next-auth and it has been very helpful, so I would like to continue using it. Thank you for creating such a great package.
I'm also curious about the state of this issue.
I added 5.0.0-beta.16 with the GitHub provider for proof-of-concept and all went well. But with Apple sign-in I ran into the current issue. When I saw it was unresolved I downgraded to v4 and ran into #4061, which I also cannot fix atm.
Is there any working example of Apple sign-in using next-auth v4 or v5?
I am also watching this closely, I am only able to use Sign in with Apple for my application, so this is key for me.
I have tried the configuration above from @ChrGrb but am getting a 'checks.state argument is missing', I suspect I am missing something dumb.
I've been watching this for a year to see if anyone came up with anything. I gave up on trying to get email from Apple via AuthJS. It's not impossible but in my estimation it looks like it would take a lot of work that's beyond my desire. These notes are for anyone else who is feeling ambitious.
In a nutshell, the valid way that Apple is providing the email scope is only through using the response_mode of 'form_post'. Search for response_mode and response_type in the Apple docs. AuthJS on the other hand only supports 'query' mode. Implementing 'form_post' in AuthJS looks non trivial.
If you want to push for this, I would start a discussion with one of the core contributors like @balazsorban44 or @ThangHuuVu . I'm neither an expert in OAuth nor deeply familiar with all of the nuances that are going on in packages/core/src/lib/[actions|pages]
Otherwise the default Apple provider config should be replaced with the one that @ChrGrb has provided above and in the Apple provider docs clearly let everyone know that they won't be getting the email scope from Apple when using AuthJS.
Note for @balazsorban44 , the docs say that you test 20 or so of the providers and we can try out the sample app. When I click on Sign In on the Apple button (the first one at the top) and try to create an account, it fails no matter which options I choose.
The basis for my findings. Search for 'form_post' in the entire repo. It only shows up in the default Apple provider source file and if you have read through this thread, you will know that the provided Apple default config doesn't work. The workaround that @ChrGrb provided will get you logged in but not an account with the email scope. Note that the response mode is 'query' in that workaround config. Now go back to the Apple docs in the link above and read the section about getting the identity_token and you will see that it's only provided in the modes of 'fragment' or 'form_post'. If you search for 'fragment' in the codebase you will not see it show up in any provider code. The user section and identity_token of the response are the only ways to get email, and they must be requested in the scope and that cant be done with 'query' mode with Apple. In the back of my mind I can hear a security guy at Apple telling the authentication team to not put email in the url, because it seems like the right call. On the other hand it seems like Apple is the only player not using 'query'.
Does anyone see any other way?
Hey @jschlesser I believe your details with regards to the Sign in with Apple are correct, I have a working implementation in Python FastAPI, and my experience there is consistent with your comments.
The only other thing I would add (or call out explicitly) is that userInfo is only returned from the first Sign in with Apple request.
In order to get the userInfo details again, you must go to https://appleid.apple.com/ and remove the app in 'Sign in with Apple' section.
Question, are the changes from https://github.com/nextauthjs/next-auth/pull/8189 (or equivalent) in the latest build or beta build?
~~If we apply that patch ourselves to the latest code, can we get things working/is that a good approach?~~ I see the 8189 pull/patch is quite out of date, so there is rebasing required.
Until the apple provider is fixed I was able to get this working by adding an override route to handle the apple callback.
// app/api/auth/[...nextauth]/route.ts
import { handlers } from 'auth'
export const { GET, POST } = handlers
// app/api/auth/callback/apple/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { GET as NextAuthGET } from '../../[...nextauth]/route'
// re-export the next-auth GET handler for this endpoint.
export { NextAuthGET as GET }
export async function POST(req: NextRequest) {
// contains user name and email if you need to process anything.
const data = await req.formData()
const queryParams: { [key: string]: string } = {}
data.forEach((value, key) => {
queryParams[key] = value.toString()
})
const searchParams = new URLSearchParams(queryParams)
const cookie = req.headers.get('cookie') || ''
// redirect to the next-auth handler which expects GET with query params.
return NextResponse.redirect(
`https://${req.headers.get('host')}/api/auth/callback/apple?` +
searchParams.toString(),
{
status: 302,
headers: {
cookie,
},
},
)
}
Until the apple provider is fixed I was able to get this working by adding an override route to handle the apple callback.
// app/api/auth/[...nextauth]/route.ts import { handlers } from 'auth' export const { GET, POST } = handlers// app/api/auth/callback/apple/route.ts import { NextRequest, NextResponse } from 'next/server' import { GET as NextAuthGET } from '../../[...nextauth]/route' // re-export the next-auth GET handler for this endpoint. export { NextAuthGET as GET } export async function POST(req: NextRequest) { // contains user name and email if you need to process anything. const data = await req.formData() const queryParams: { [key: string]: string } = {} data.forEach((value, key) => { queryParams[key] = value.toString() }) const searchParams = new URLSearchParams(queryParams) const cookie = req.headers.get('cookie') || '' // redirect to the next-auth handler which expects GET with query params. return NextResponse.redirect( `https://${req.headers.get('host')}/api/auth/callback/apple?` + searchParams.toString(), { status: 302, headers: { cookie, }, }, ) }
I tried this route handler, but the authentication process seems still to be missing the email address from the Apple account. Here's the error log from the related Vercel function:
[31m[auth][cause][0m: u: null value in column "email" of relation "user" violates not-null constraint at K (/var/task/.next/server/chunks/458.js:420:2291) at /var/task/.next/server/chunks/458.js:420:3320 at Socket.eQ (/var/task/.next/server/chunks/458.js:420:3325) at Socket.emit (node:events:517:28) at addChunk (node:internal/streams/readable:368:12) at readableAddChunk (node:internal/streams/readable:341:9) at Readable.push (node:internal/streams/readable:278:10) at TCP.onStreamRead (node:internal/stream_base_commons:190:23) at TCP.callbackTrampoline (node:internal/async_hooks:128:17)
@glencoden as @gordonturner mentioned the user's email will only be delivered the first time they sign in. Subsequent sign ins will deliver the apple sub which is used to retrieve an account by id along with the associated user. After signing in with apple are you prompted to share or mask your email address?
I'm not sure what your adapter looks like but the PrismaAdapter is working fine out of the box for me with 5.0.0-beta.17
Hey everyone, thanks for discussing about this issue. I want to summarize the findings & workaround so far to track it better. I believe there are some separate issues that are happening at once, and we can deal with them one by one before getting the Apple provider working.
Issue 1: @auth/core is throwing an error: TypeError: TODO: Authorization server did not provide a userinfo endpoint.
https://github.com/nextauthjs/next-auth/blob/7e2c2d4509e7de83040dabc14b6d92bb105b9de4/packages/core/src/lib/actions/callback/oauth/callback.ts#L58-L61
See Apple's .well-known. @auth/core is requiring userinfo_endpoint although it is technically optional from the spec. I might be wrong here. I read through the OpenID Connect Core Spec, but couldn't find the linfo about the userinfo endpoint being required for the Authorization Server.
Plan to fix: I will confirm with @balazsorban44 before raising a PR
Issue 2: Apple requires response_mode=form_post in the Authorization request if requesting for any scopes
Thanks @jschlesser for pointing to this info on the Apple documentation page. Currently, @auth/core isn't supporting the response_mode=form_post.
Plan to fix: @ChrGrb raised a PR to fix here: https://github.com/nextauthjs/next-auth/pull/8428
Issue 3: Apple isn't returning the claims in id_token
Auth.js normally expects the claims are in id_token if the provider is OIDC type.
https://github.com/nextauthjs/next-auth/blob/7e2c2d4509e7de83040dabc14b6d92bb105b9de4/packages/core/src/lib/actions/callback/oauth/callback.ts#L157
Instead, Apple returns the user info as a JSON string in the query params, for reasons only they know. This is not an issue with Auth.js.
Workaround
To workaround the issues, you can use patch-package following this PR change: https://github.com/nextauthjs/next-auth/pull/8189/. You could check the PR comments for details of why we haven't merged it yet.
Hope that helps. I also confirm that our deployed example isn't working as expected at the moment. The Apple provider is indeed annoying to work with:
- It requires a fee for a developer account.
- The account then requires 2FA on phone only, which makes it even harder to share the account between contributors
To everyone facing issues with the Apple provider not providing a userinfo endpoint, here's a workaround to get it functioning correctly with NextAuth.js:
Configuration for Apple Provider in NextAuth.js
- Configure the Apple provider in your NextAuth.js configuration file, typically found in
pages/api/auth/[...nextauth].ts:
import NextAuth from "next-auth"
import Providers from "next-auth/providers"
export default NextAuth({
providers: [
Providers.Apple({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET,
wellKnown: "https://appleid.apple.com/.well-known/openid-configuration",
checks: ["pkce"],
authorization: {
url: 'https://appleid.apple.com/auth/authorize',
params: {
scope: "name email",
response_type: "code",
response_mode: "form_post",
state: crypto.randomUUID(),
},
},
token: {
url: `https://appleid.apple.com/auth/token`,
},
client: {
token_endpoint_auth_method: "client_secret_post",
},
profile(profile) {
return {
id: profile.sub,
name: profile.name || null,
email: profile.email || null,
image: null,
}
},
profileConform(profile, query) {
if (query.user) {
const user = JSON.parse(query.user);
if (user.name) {
profile.name = Object.values(user.name).join(" ");
}
}
return profile;
},
}),
],
session: {
jwt: true,
},
jwt: {
secret: process.env.AUTH_SECRET,
},
},
})`
Override the Apple Callback Route
To handle the Apple callback correctly, create an override route for the Apple callback:
- Create a file at
pages/api/auth/callback/apple.ts:
import { NextRequest, NextResponse } from 'next/server'
import { GET as NextAuthGET } from '../../[...nextauth]'
export { NextAuthGET as GET }
export async function POST(req: NextRequest) {
const data = await req.formData();
const queryParams: { [key: string]: string } = {};
data.forEach((value, key) => {
queryParams[key] = value.toString();
});
const searchParams = new URLSearchParams(queryParams);
const cookie = req.headers.get('cookie') || '';
return NextResponse.redirect(
`https://${req.headers.get('host')}/api/auth/callback/apple?${searchParams.toString()}`,
{
status: 302,
headers: {
cookie,
},
);
}
Explanation:
-
Apple Provider Configuration:
- The
authorizationparameter includesresponse_mode: "form_post"to comply with Apple's requirements. - The
profileConformfunction processes the user information returned in the authorization response.
- The
-
Session and JWT:
- The session is configured to use JWT, and the
jwtconfiguration includes the secret required for encoding.
- The session is configured to use JWT, and the
-
PKCE Code Verifier Cookie:
- A custom cookie configuration for
pkceCodeVerifierto handle the PKCE flow securely.
- A custom cookie configuration for
-
Handling the Apple Callback:
- The POST handler processes the form data returned from Apple, converts it to query parameters, and redirects it to the NextAuth.js handler with the necessary cookies.
By following these steps, you should be able to implement a workaround for the Apple provider issue in NextAuth.js until a permanent fix is merged into the library.
Any idea when this will be updated to work by default?
Thanks to all for workarounds here. Just for reference, on latest beta 5.0.0-beta.20 thanks to @ChrGrb and #8428 there not need to override callback. In my case I decided not to try to get the name, and just allow user to change it. Here is my working configuration
export const authConfig = {
cookies: {
pkceCodeVerifier: {
name: "next-auth.pkce.code_verifier",
options: {
httpOnly: true,
sameSite: "none",
path: "/",
secure: true,
},
},
},
providers: [
GitHub,
Google,
Apple({
clientId: process.env.AUTH_APPLE_ID,
clientSecret: ""+process.env.AUTH_APPLE_SECRET,
checks: ["pkce"],
token: {
url: `https://appleid.apple.com/auth/token`,
},
client: {
token_endpoint_auth_method: "client_secret_post",
},
authorization: {
params: {
response_mode: "form_post",
response_type: "code",//do not set to "code id_token" as it will not work
scope: "name email"
},},
profile(profile) {
return {
id: profile.sub,
name: "Person Doe",//profile.name.givenName + " " + profile.name.familyName, but apple does not return name...
email: profile.email,
image: "",
}
}
}),
}
@sl45sms your working configuration works great, thank you!
@sl45sms thx, saved my day!
I am getting this error, I believe because I am using the AUTH_REDIRECT_PROXY documented here. https://authjs.dev/getting-started/deployment#securing-a-preview-deployment
InvalidCheck: Missing state in query, but required for redirect proxy. Read more at https://errors.authjs.dev#invalidcheck
Also, getting this error in production where the redirect proxy should be used
Error: TODO: Handle OIDC response body error.
I am using https://github.com/t3-oss/create-t3-turbo as a starter. I need to implement apple auth before I can publish to the app store so this is a major blocker, I believe for all of the project using this template.
Is there another workaround?
I am getting this error, I believe because I am using the AUTH_REDIRECT_PROXY documented here. https://authjs.dev/getting-started/deployment#securing-a-preview-deployment
InvalidCheck: Missing state in query, but required for redirect proxy. Read more at https://errors.authjs.dev#invalidcheckAlso, getting this error in production where the redirect proxy should be used
Error: TODO: Handle OIDC response body error.I am using https://github.com/t3-oss/create-t3-turbo as a starter. I need to implement apple auth before I can publish to the app store so this is a major blocker, I believe for all of the project using this template.
Is there another workaround? @kiikoh Try to set state cookie with samesite none (as pkceCodeVerifier do)
@sl45sms I have update my config to include this, in both the proxy and the nextjs route
cookies: {
pkceCodeVerifier: {
name: "next-auth.pkce.code_verifier",
options: {
httpOnly: true,
sameSite: "none",
path: "/",
secure: true,
},
},
state: {
// do i need to add a name? what should it be
options: {
httpOnly: true,
sameSite: "none",
path: "/",
secure: true,
},
},
},
Still seeing the same errors
In production, without the proxy
Error: TODO: Handle OIDC response body error
at ig (/var/task/apps/nextjs/.next/server/chunks/621.js:393:29432)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async iP (/var/task/apps/nextjs/.next/server/chunks/621.js:393:35570)
at async iU (/var/task/apps/nextjs/.next/server/chunks/621.js:393:47370)
at async iK (/var/task/apps/nextjs/.next/server/chunks/621.js:393:52671)
at async /var/task/apps/nextjs/.next/server/chunks/189.js:13:50805
at async /var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:34666
at async eS.execute (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:25813)
at async eS.handle (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:35920)
at async es (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:16:25461)
And
Missing state in query, but required for redirect proxy on local development, which uses the proxy
Thank you for taking a look into this
@kiikoh are you sure you have set the correct clientId? I had some strange problems because of this, which didn't seem to be related at all.
@kiikoh are you sure you have set the correct clientId? I had some strange problems because of this, which didn't seem to be related at all.
I'm pretty sure it's the correct client id because on apples login page it shows my app name.
@sl45sms
@kiikoh apple call this as "serviceID" and you can found it under https://developer.apple.com/account/resources/identifiers/list/serviceId
looks like
in my case is "ai.prompt2" may you use the app id
@kiikoh apple call this as "serviceID" and you can found it under developer.apple.com/account/resources/identifiers/list/serviceId looks like
in my case is "ai.prompt2" may you use the app id
@sl45sms right, I was just saying that since that text appeared, and I didn't manually enter that in it must have picked it up correctly. Everything works up until the redirect back to my app.
In my env I have, AUTH_APPLE_ID=com.musicbridge.authsrv
in my case is "ai.prompt2" may you use the app id