-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
v3 - CALLBACK_OAUTH_ERROR (Invalid state...) - Custom Provider Azure AD B2C #468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I believe the current option for this is 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 |
Gotcha, Is the gist of this, then, that the I will close this. If I find out anything useful about what Azure B2C is doing I'll follow up. Thanks! |
Hey @BenjaminWFox-Lumedic , so far connecting with b2c has been a tedious process. Any chance you could share your full setup for using next-auth with AD b2c? |
@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 |
@BenjaminWFox-Lumedic With the above config code accessToken is undefined, were you able to make it work?. |
@WaysToGo my example is using JWTs, so I believe you will have to use a database for your scenario, but I have not explored that route so can't comment specifically on implementation details :( |
@WaysToGo in case this is relevant, I hadn't noticed before but there is the option to include 'Identity Provider Access Token' as a claim in the Azure B2C user flow. I didn't have that box checked in my walkthrough, unsure if checking it (if you haven't already) would help in your case. |
@BenjaminWFox I was able to get the access token by using a different scope URL, which is mentioned here |
@BenjaminWFox @WaysToGo I have read several times both of your setups and I have setup my own on azure and looks like I cannot even get the access token from azure. Which bring me to the question do I actually need this access token to work with my api? If I have refresh token it means that I can control user when I need and how I need, but if the token expires it should self logout from a system, correct? sorry for those odd questions I am new to Azure and NextAuth. |
@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 Additionally, you can see the logic in there that checks for imminent expiration of the import NextAuth from 'next-auth'
// import Providers from 'next-auth/providers'
const tenantName = process.env.B2C_AUTH_TENANT_NAME
const loginFlow = process.env.B2C_LOGIN_FLOW_NAME
const maxAge = Number(process.env.CLIENT_SESSION_MAX_AGE)
const jwtSecret = process.env.NEXTAUTH_JWT_SECRET
const appSecret = process.env.NEXTAUTH_APP_SECRET
const clientId = process.env.B2C_AUTH_CLIENT_ID
const clientSecret = process.env.B2C_AUTH_CLIENT_SECRET
const apiAccessClaim = process.env.B2C_API_ACCESS_CLAIM
const signingKey = process.env.NEXTAUTH_SIGNING_KEY
const encryptionKey = process.env.NEXTAUTH_ENCRYPTION_KEY
const encryption = process.env.NEXTAUTH_ENCRYPT_JWT
const tokenUrl = `https://${tenantName}.b2clogin.com/${tenantName}.onmicrosoft.com/${loginFlow}/oauth2/v2.0/token`
const options = {
session: {
jwt: true,
maxAge,
},
jwt: {
secret: jwtSecret,
encryption,
signingKey,
encryptionKey,
},
secret: appSecret,
pages: {
signOut: '/auth/signout',
},
providers: [
{
id: 'azureb2c',
name: 'Azure B2C',
type: 'oauth',
version: '2.0',
debug: true,
scope: `https://${tenantName}.onmicrosoft.com/api/${apiAccessClaim} offline_access openid`,
params: {
grant_type: 'authorization_code',
},
accessTokenUrl: tokenUrl,
requestTokenUrl: tokenUrl,
authorizationUrl: `https://${tenantName}.b2clogin.com/${tenantName}.onmicrosoft.com/${loginFlow}/oauth2/v2.0/authorize?response_type=code+id_token+token&response_mode=form_post`,
profileUrl: 'https://graph.microsoft.com/oidc/userinfo',
profile: (profile) => {
console.debug('\n')
console.debug('~~ PROFILE', profile)
console.debug('\n')
// The NextAuth `user` object available to the client
return {
id: profile.oid,
name: `${profile.given_name} ${profile.family_name}`,
email: profile.emails.length ? profile.emails[0] : null,
image: undefined,
}
},
clientId,
clientSecret,
idToken: true,
state: false,
},
],
callbacks: {
// eslint-disable-next-line no-unused-vars
jwt: async (token, user, account, profile, isNewUser) => {
const now = parseInt(Date.now() / 1000, 10)
const tokenExpiryPaddingSeconds = 30
const isSignIn = !!user
// Add auth_time to token on signin in
if (isSignIn) {
// eslint-disable-next-line no-param-reassign
token.b2c = {
accessToken: account.accessToken,
refreshToken: account.refreshToken,
iat: profile.iat,
exp: profile.exp,
}
}
console.debug('\n Token expires / refreshes in: ', token.b2c.exp - now, ' / ', (token.b2c.exp - tokenExpiryPaddingSeconds) - now)
/**
* Add some extra seconds to refresh before it is completely expired to avoid
* any edge case scenarios where the token expires in transit if it is almost
* expired, but validated prior to an API call, and then sent in the
* Authorization header and expires.
*/
if (token.b2c.exp - tokenExpiryPaddingSeconds < now) {
const refreshQuery = `?grant_type=refresh_token&refresh_token=${token.b2c.refreshToken}&scope=https://${tenantName}.onmicrosoft.com/api/${apiAccessClaim} offline_access openid&client_id=${process.env.B2C_AUTH_CLIENT_ID}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_secret=${process.env.B2C_AUTH_CLIENT_SECRET}`
const response = await fetch(`${tokenUrl}${refreshQuery}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
try {
const result = await response.json()
// eslint-disable-next-line no-param-reassign
token.b2c = {
accessToken: result.access_token,
refreshToken: result.refresh_token,
iat: result.not_before,
exp: result.expires_on,
}
}
catch (e) {
console.error('There was an error trying to refresh the accessToken')
console.error(e)
}
}
return Promise.resolve(token)
},
},
}
export default (req, res) => NextAuth(req, res, options) |
@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 |
Is it possible to use state with azure b2c? Without it you can only redirect back to the home page and you cant handle things like "Forgot my Password" |
@BenjaminWFox weird question, why dont make |
can you please show me how to use B2C with authorization code flow with PKCE, without Implicit Grant ? Thanks |
Now that nextauth has an official AzureAdB2C provider, does anyone have an example of token refreshing which uses this method? |
@jeremylynch be aware that the current provide marked AzureADB2C is not actually implementing AzureADB2C. There's a PR to address this on the If I were to implement token refreshing with the Provider in that PR I'd do it in pretty much the same way as in my comment. |
@BenjaminWFox I am trying to connect next-auth with Azure AD B2C. this is my code import NextAuth from 'next-auth'; const options:any = { export default (req, res) => NextAuth(req, res, options); but I am getting an error like this [next-auth][error][oauth_get_access_token_error] |
@parantha97 @BenjaminWFox I'm also getting the above error, any leads to solve this? |
@kiranupadhyak I reverted to next-auth version 3.13.3. it worked for me. but It's not a solution. |
@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! 🙏 |
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: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:
The B2C authorize url looks like:
The Form Data response includes (along with the code & id_token):
The next-auth error looks like:
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.
The text was updated successfully, but these errors were encountered: