Organisation Member Invitation flow results in state mismatch

Please include the following information in your post:

SDK: nextjs-auth0
SDK Version: 1.5.0
Platform Version: Node v16.2.0

We are attempting to invite users to an organisation and are hitting an issue whereby the callback endpoints (within the default nextjs-auth0 /api/auth/callback endpoint is failing due to a state mismatch error).

Here are the steps taken:

  1. Create Organisation using Management API (Create Organizations)

  2. Enable username/password connection for that Organisation using management API (Enable Organization Connections)

  3. Inviting a list of users to the newly created organisation using the Send Membership Invitations API (Send Organization Membership Invitations)

  4. Invitee receives the invitation email with a login link (defined in tenant settings) which includes the invitation and organization parameters. Example URL https://{host}/api/auth/login?invitation=xgHtmW8bgR0MdvMM3hM9yCPdKE6ufG9Y&organization=org_ssaeGaKNJBfrfsQ&organization_name=test

  5. Invitee receives the invitation email with a login link (defined in tenant settings) which includes the invitation and organization parameters. Example URL https://{host}/api/auth/login?invitation=xgHtmW8bgR0MdvMM3hM9yCPdKE6ufG9Y&organization=org_ssaeGaKNJBfrfsQ&organization_name=test

  6. Modified the default login handler that nextjs-auth0 provides in order to forward the incoming organization and invitation params and set them as custom authorization params.

export default async function loginHandler(req: NextApiRequest, res: NextApiResponse) {
    return await handleLogin(req, res, {
        authorizationParams: {
            organization: req.query.organization as string,
            invitation: req.query.invitation as string,
        },
    });
}
  1. The library redirects the user to the /authorize endpoint, forwarding the organization and invitation values and the user is presented with the custom organisation-branded invitation acceptance screen.

  2. The user enters a password, and clicks continue. They are presented with a consent page where they authorize the app.

  3. When accepted, auth0 redirects back to the callback handler in nextjs-auth0 with code and state parameters. http://{host}/api/auth/callback?code=QgwBGJrS5suZZMBe&state=eyJyZXR1cm5UbyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMS8ifQ

This is where the callback handler in the SDK fails with an Error stating that thereā€™s a state mismatch:

state mismatch, expected eyJyZXR1cm5UbyI6Ii8ifQ, got: eyJyZXR1cm5UbyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMS8ifQ

When decoding these state JWTs itā€™s because the returnTo values are different.
SDK expects:

{
  "returnTo": "/"
}

Auth0 is sending:

{
  "returnTo": "http://localhost:3001/"
}

Question is why is Auth0 sending this value and why are these values different during the invitation process?

To attempt to fix this issue, I have added an implementation for getLoginState option in the handleLogin function, in order to send a fixed returnTo value of ā€˜http://localhost:3001/ā€™.

    return await handleLogin(req, res, {
        getLoginState: () => {
            return {
                redirectTo: 'http://localhost:3001/',
            };
        },
        authorizationParams: {
            organization: req.query.organization as string,
            invitation: req.query.invitation as string,
        },

But this results in the following error:

Error [BadRequestError]: checks.state argument is missing

This means the user hits an ā€œInternal Server Errorā€ page. The interesting this is that the whole invitation process seems to have work, and the invitee does have an active session. But the callback failing prevents the end-to-end flow from working seamlessly.

Any help would be greatly apprechiated.

1 Like

hey @nicky.thorne Iā€™m having this exact issue right now. Did you manage to find a solution?

My guess here is that itā€™s because you are ignoring the opts from the originating request.

I was able to override the default login function like so, and it worked totally fine.

import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';

export default handleAuth({
  login: (req, res, opts) => {
    return handleLogin(req, res, {
      ...opts,
      authorizationParams: {
        ...opts?.authorizationParams,
        invitation: getSingleOrUndefined(req.query.invitation),
        organization: getSingleOrUndefined(req.query.organization),
      },
    });
  },
});

function getSingleOrUndefined(
  item: string | string[] | undefined
): string | undefined {
  if (!item) {
    return undefined;
  }
  const newItem = Array.isArray(item) ? item[0] : item;
  return !newItem || newItem === 'undefined' ? undefined : newItem;
}

Iā€™m not sure the implications of overriding the default login to include this. Honestly, it seems like this should just be baked in to the nextjs-auth0 library. Why do we have to override those so that invites are supported? Makes no sense really.

I had to spend a ton of time tracking this down when I sought out how to exactly invite someone to an organization, only to find that the default API endpoints donā€™t support it without modifications!

1 Like

Thanks for sharing it with the rest of community!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.