next-auth: v3 - CALLBACK_OAUTH_ERROR (Invalid state...) - Custom Provider Azure AD B2C
Have moved my initial v2 implementation to v3. It was very easy! Everything works as far as I can tell except for the state parameter.
Describe the bug
Using custom provider Azure AD B2C next-auth gives an error (see below) in the callback after successful authentication with Azure AD B2C. This seems to happen whether or not I set useState: false.
This is a new after moving to v3. The same config had worked in v2.
B2C supports the state parameter. I don’t think this is an issue with the authorization server, but I could be mistaken. From the B2C link, state is supported as:
A value included in the request that’s also returned in the token response. It can be a string of any content that you want. A randomly generated unique value is typically used for preventing cross-site request forgery attacks. The state is also used to encode information about the user’s state in the application before the authentication request occurred, such as the page they were on.
I’ve confirmed (see below) that the state provided by the authorization request is the same as the state returned from the authorization server.
To Reproduce If this is not something obvious I might have missed please let me know, and I will set up a minimal reproduction.
Expected behavior I would expect that if the state provided initially by the client & sent back by the authorization server are the same than I should not get an error.
Screenshots or error logs The B2C Custom Provider looks like:
{
id: 'azureb2c',
name: 'Azure B2C',
type: 'oauth',
version: '2.0',
debug: true,
scope: 'offline_access openid',
// params: {
// grant_type: 'authorization_code',
// },
accessTokenUrl: `https://${tenantName}.b2clogin.com/${tenantName}.onmicrosoft.com/${userFlow}/oauth2/v2.0/token`,
// requestTokenUrl: 'https://login.microsoftonline.com/${process.env.DIRECTORY_ID}/oauth2/v2.0/token',
authorizationUrl: `https://${tenantName}.b2clogin.com/${tenantName}.onmicrosoft.com/${userFlow}/oauth2/v2.0/authorize?response_type=code+id_token&response_mode=form_post`,
profileUrl: 'https://graph.microsoft.com/oidc/userinfo',
profile: (profile) => {
console.log('THE PROFILE', profile)
return {
id: profile.oid,
fName: profile.given_name,
lName: profile.surname,
email: profile.emails.length ? profile.emails[0] : null,
}
},
clientId: process.env.AUTH_CLIENT_ID,
clientSecret: process.env.AUTH_CLIENT_SECRET,
idToken: true,
// useState: false,
},
The B2C authorize url looks like:
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{flow}/oauth2/v2.0/authorize
?response_type=code+id_token
&response_mode=form_post
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fcallback%2Fazureb2c
&scope=offline_access%20openid
&state=45e76b516360e8aa4e79d44661344ba06f8ed0fc8a08beb3362bcbe7cde2fe90
&client_id=<client_id>
The Form Data response includes (along with the code & id_token):
state: 45e76b516360e8aa4e79d44661344ba06f8ed0fc8a08beb3362bcbe7cde2fe90
The next-auth error looks like:
[next-auth][error][callback_oauth_error] Error: Invalid state returned from oAuth provider
at <irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:46:27
at Generator.next (<anonymous>)
at asyncGeneratorStep (<irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:26:103)
at _next (<irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:28:194)
at <irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:28:364
at new Promise (<anonymous>)
at <irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:28:97
at <irrelevant-project-path>/node_modules/next-auth/dist/server/lib/oauth/callback.js:143:17
at <irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:58:31
at Generator.next (<anonymous>)
at asyncGeneratorStep (<irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:26:103)
at _next (<irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:28:194)
at <irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:28:364
at new Promise (<anonymous>)
at <irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:28:97
at <irrelevant-project-path>/node_modules/next-auth/dist/server/routes/callback.js:302:17
https://next-auth.js.org/errors#callback_oauth_error
Additional context No additional context I can think of.
Documentation feedback Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.
- Found the documentation helpful
- Found documentation but was incomplete
- Could not find relevant documentation
- Found the example project helpful
- Did not find the example project helpful
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 20 (6 by maintainers)
@RobbyUitbeijerse I created a minimal example repo and wrote up the steps I took to set it up - take a look, let me know if anything in it is off: https://benjaminwfox.com/blog/tech/how-to-configure-azure-b2c-with-nextjs
@gyto23 There is an extra step needed to get an access token from Azure B2C (assuming you’re using B2C) - you’ll need to follow the steps from this tutorial documentation: https://docs.microsoft.com/en-us/azure/active-directory-b2c/access-tokens
The main points are:
I would not use the refresh token as any means of authentication/authorization. I’m not versed enough to tell you why it’s a bad idea, but I’m confident that it is 😃
I have an updated config I can share with you which may help. Following the steps in the link above to get the access token is worth the extra effort, as it allows you to much better control the B2C JWT, which is separate and distinct from the NextAuth JWT.
In the
jwtcallback with this setup theaccounthas anaccessToken(which is the B2C JWT) andrefreshTokenboth of which I store on the NextAuth JWT. Then it’s easy to use the NextAuth API in my NextJS API to get theaccessTokenfrom the JWT and pass it to my backend api.Additionally, you can see the logic in there that checks for imminent expiration of the
accessTokenand then silently gets a new token if needed. If you instead wanted to not refresh the token you could just revoke the users NextAuth session at that point.@parantha97 I would highly appreciate if you opened a new bug report with a reproduction, so we can have a look and fix any potential issues introduced after that version! 🙏
@kiranupadhyak I reverted to next-auth version 3.13.3. it worked for me. but It’s not a solution.
@BenjaminWFox Thank you for your feedback, this is really helps. I hope you going to update the tutorial for the Azure setup for those people who searching similar implementation
@BenjaminWFox I was able to get the access token by using a different scope URL, which is mentioned here https://docs.microsoft.com/en-us/azure/active-directory-b2c/add-web-api-application?tabs=app-reg-ga current scope config looks something like this
scope :https://${tenantName}.onmicrosoft.com/api/demo.read openid offline_access,I believe the current option for this is
state: true(and thatuseStateis from an earlier v3 beta).It’s safe to use that option if a particular provider is doing something unexpected.
The only built in Provider I know has a problem with it is Apple.
The token used to verify state is generated is from the
csrfToken- if that isn’t being set for any reason then you might run into a problem, but in v3 you shouldn’t be able to start an authorisation flow withoutcsrfTokenbeing set.