next-auth: Using custom provider (Microsoft/MS Graph) - Error: Missing or invalid provider account

Great library and docs!

I’ve been trying to add a custom provider (https://docs.microsoft.com/en-us/graph/auth-v2-user) and have been successful to the point of acquiring a auth code and profile information. However, after the logging in, I get this error: [next-auth][error][OAUTH_CALLBACK_HANDLER_ERROR] [ Error: Missing or invalid provider account]

My [...nextauth].js config is site: process.env.SITE || ‘http://localhost:3000’, providers: [ { id: ‘msal’, name: ‘xxx’, type: ‘oauth’, version: ‘2.0’, scope: ‘https://graph.microsoft.com/user.read’, params: { grant_type: ‘authorization_code’ }, accessTokenUrl: ‘https://login.microsoftonline.com/xxx/oauth2/v2.0/token’, requestTokenUrl: ‘https://login.microsoftonline.com/xxx/oauth2/v2.0/authorize’, authorizationUrl: ‘https://login.microsoftonline.com/xxx/oauth2/v2.0/authorize?response_type=code’, profileUrl: ‘https://graph.microsoft.com/oidc/userinfo’, profile: (profile) => { console.log(profile) return { id: profile.id, name: profile.name, email: profile.email, image: profile.picture } }, clientId: process.env.MS_CLIENT_ID, clientSecret: process.env.MS_CLIENT_SECRET } ],

The console.log that is there returns successfully.

Am I missing anything? or is there anywhere else I’d need to add provider information? I’ve had a look at the other providers that are built-in and couldn’t see any additional config. Appreciate any direction 😃

  • 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
  • Reactions: 4
  • Comments: 25 (6 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks everyone for the helpful thread. My successful implementation is:

In [...nextauth.js] file:

import NextAuth from 'next-auth';

const options = {
  providers: [
    {
      id: 'msal',
      name: 'Microsoft Login',
      type: 'oauth',
      version: '2.0',
      scope: 'https://graph.microsoft.com/user.read',
      params: { grant_type: 'authorization_code' },
      accessTokenUrl: process.env.MSAL_TOKEN_ACCESS,
      requestTokenUrl: process.env.MSAL_TOKEN_REQUEST,
      authorizationUrl: process.env.MSAL_TOKEN_AUTHORIZE,
      profileUrl: 'https://graph.microsoft.com/v1.0/me/',
      profile: profile => {
        console.log(profile);
        return {
          id: profile.id,
          name: profile.displayName,
          last_name: profile.surname,
          first_name: profile.givenName,
          email: profile.mail,
        };
      },
      clientId: process.env.APPLICATION_ID,
      clientSecret: process.env.AUTH_SECRET,
    },
  ],
};

export default (req, res) => NextAuth(req, res, options);

Then be sure to add the proper redirect URIs into the App Registration on the Active Directory: http:// localhost:3000/api/auth/signin/msal http:// localhost:3000/api/auth/callback/msal

Thanks for putting together this awesome pkg.

@iaincollins This provider is working. I think it can be added to the lib. Just sayin’

@BrendyRyan I ended up using callbacks and decoding the jwt to get the roles. This way it first adds it to the token and then to the session so it is accessible on frontend. (I also needed accesstoken so it is done same way)

callbacks: {
     session: async (session: any, user: any) => {
       session.accessToken = user.accessToken;
       session.roles = user.roles;
       session.upn = user.upn
       return Promise.resolve(session)
     },
    jwt: async (token, user, account, profile, isNewUser) => {
        const isSignIn = (user) ? true : false
        if (isSignIn) {
            token.accessToken = account.accessToken;

            const decoded: any = jwt_decode(token.accessToken)
            token.roles = decoded.roles
            token.upn = decoded.upn
        }
        
        return Promise.resolve(token)
    }
  }

@iaincollins If it makes you feel better the documentation of the callbacks is not bad at all. More examples/use cases are always rad of course, but you probably have more than enough other things to work on!

The real problem was that I didn’t scroll the page and stopped reading after the first note to actually know how the requirements, so not realizing a return/resolution was required 🤦‍♂️ so was just console.logging to see the passed arguemnt values.

callbacks: {
  signin: async (profile, account, metadata) => { },
  redirect: async (url, baseUrl) => { },
  session: async (session, token) => { },
  jwt: async (token, oAuthProfile) => { }
}

@BenjaminWFox-Lumedic Thanks! That worked! It successfully signs in and checking the callbacks, returns the correct profile info and token. It successfully authenticates and redirects.

However…when I try to use useSession or ‘getSession’ (accessing 'http://localhost:3000/api/auth/session`), it continues to return { "user": { "name": null, "email": null, "image": null }, "expires": "2020-08-15T02:40:48.226Z" }

Did you encounter this as well?

I’ve also been fighting through this. I’m using Azure B2C and not MSAL but this may be a similar problem - check your console.log(profile) token properties actually line up with what you’re trying to extract.

For instance, you may need to access profile.oid instead of profile.id, there may not be a profile.email property at all, etc…

I had this same issue, and removing/fixing the incorrect properties resolved it.

This is available in the canary release (npm i next-auth@canary), see the config: https://www.github.com/nextauthjs/next-auth/tree/canary/src%2Fproviders%2Fazure-ad-b2c.js

Also, it looks like @BrendyRyan anwered it here: https://github.com/nextauthjs/next-auth/issues/429#issuecomment-716884585

@iaincollins This is great! I ran across this issue today creating a custom provider

{
  profile: { name: ' ', email: null, image: undefined },
  account: {
    provider: 'bluebutton-gov',
    type: 'oauth',
    id: undefined,
    refreshToken: 'ajnvY6SlNJn2VRGrdh3QhJfrZ6JME1',
    accessToken: 'w6ehZzsroOwSuxWp1TTBxS1K6Af5',
    accessTokenExpires: null
  },
  OAuthProfile: {
    sub: '-20140000004807',
    name: ' ',
    given_name: '',
    family_name: '',
    email: '',
    iat: '2019-12-26T21:39:15.894Z',
    patient: '-20140000004807'
  }
}

I used the sub instead of profile.id;

@ronaldh46 I actually wanted to create a graph provider the one thing I would look at as well: (Check the token configuration )

AzureAD -> App registrations-> (Your App) -> Token configuration

image

@ronaldh46 One other note since I just discovered something else I did wrong - if you have the jwt set in callbacks and are not returning + resolving the token your session details will be undefined.

  callbacks: {
    jwt: async (token) => {
      console.log('CALLBACK: jwt', token)

      return Promise.resolve(token)
    },
  },

@ronaldh46 Nice! Thanks for posting your original question too, your note on the console.log pointed me in the right direction.

Regarding your profile object, those are null because (I’m assuming) they aren’t mapped in the next-auth profile method. They also may not be returned at all by MSAL depending on how your App Registration/Graph calls are set up.

I can’t provide implementation specifics because my scenario is a little different (Azure B2C vs MSAL), but from the link you provided, in order to populate those fields you’ll have to map the information you receive from the graph API. And note that you can return properties mapped however you like, which from their sample payload might look something like this:

profile: (profile) => {
  console.log(profile)
  return {
    id: profile.id,
    fName: profile.givenName,
    lName: profile.surname,
    email: profile. userPrincipalName,
    name: `${profile.givenName} ${profile.surname}`,
  }
},