auth icon indicating copy to clipboard operation
auth copied to clipboard

I get Nonces mismatch error for Azure provider when using supabase.auth.signInWithIdToken()

Open mike-rambil opened this issue 11 months ago • 5 comments

Error Name


Nonces mismatch

Code

import TokenAndAssertionFetch from '@/components/AzureAuth/TokenAndAssertionFetch';
import { createClient } from '@/utils/supabase/client';
import { useEffect, useState } from 'react';

function decodeJWT(token) {
  try {
    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const jsonPayload = decodeURIComponent(
      atob(base64)
        .split('')
        .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
        .join('')
    );
    return JSON.parse(jsonPayload);
  } catch (error) {
    console.error('Error decoding JWT:', error);
    return null;
  }
}

export default function Page() {
  const [user, setUser] = useState(null);
  const [errors, setErrors] = useState(null);
  const supabase = createClient();
  const { assertion: token } = TokenAndAssertionFetch();

  useEffect(() => {
    const signIn = async () => {
      if (token) {
        try {
          // Decode token to check if nonce exists
          const decodedToken = decodeJWT(token);
          console.log('Decoded token:', decodedToken);

          // Prepare sign in options
          const signInOptions = {
            provider: 'azure',
            token,
          };

          // Only add nonce if it exists in the token
          if (decodedToken?.nonce) {
            signInOptions.nonce = decodedToken.nonce;
          }

          const { data, error } = await supabase.auth.signInWithIdToken(
            signInOptions
          );

          if (error) {
            setErrors(error.message);
            console.error('Sign in error:', error);
          } else {
            setUser(data.user);
          }
        } catch (err) {
          setErrors(err.message);
          console.error('Unexpected error:', err);
        }
      }
    };

    signIn();
  }, [token]);

  return (
    <div className='p-4'>
      {errors && (
        <div
          className='bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4'
          role='alert'
        >
          <strong className='font-bold'>Error:</strong>
          <span className='block sm:inline'> {errors}</span>
        </div>
      )}
      {user ? (
        <div
          className='bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded'
          role='status'
        >
          <strong className='font-bold'>Success!</strong>
          <span className='block sm:inline'>
            {' '}
            User authenticated: {user.email}
          </span>
        </div>
      ) : (
        <div
          className='bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded'
          role='status'
        >
          <span className='block sm:inline'>Authenticating...</span>
        </div>
      )}
    </div>
  );
}

UI

image

things to know

when decoding the token coming from azure using the jwt decoder, the token has the nonce passed in.

 ........
  "name": "Micheal Palliparambil",
  "nonce": "<<redacted>>",
  ........
  ........
  ........
  ........
  ........
  ........
  (all other entries redacted since its irrelevant)
}

mike-rambil avatar Jan 27 '25 04:01 mike-rambil

I'm having the same issue. The nonce I'm using to request the id_token is the same shown in my decoded id_token

  const baseUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
  const params = new URLSearchParams()
  params.set("client_id", CLIENT_ID)
  params.set("redirect_uri", REDIRECT_URI)
  params.set("response_type", "id_token")
  ....
  params.set("nonce", <myNonce>)
{
  ver: '',
  iss: '',
  sub: '',
  aud: '',
  .....
  nonce: <myNonce>,
}

but when I pass it to supabase

const {data, error} =await supabase.auth.signInWithIdToken({
    token: id_token,
    provider: "azure",
    nonce: <myNonce>
  })

I get this error

AuthApiError: Nonces mismatch


An Alternative

Use the code response type instead of id_token as mentioned here

~~You may need to change the settings in your Azure application to include ID Tokens. I would get an id_token from most accounts using the openid scope but not always. I've just checked these boxes and am hoping it solves that inconsistency problem.~~

(the settings in azure are for hybrid or implicit flows only and still requires a nonce)

I have found that the auth code flow inconsistently returns an ID Token. YMMV

brown62 avatar Jan 27 '25 18:01 brown62

@brown62 are you suggesting that using "code" instead would help? Either way thanks for the info :)

mike-rambil avatar Jan 29 '25 17:01 mike-rambil

@mike-rambil Yeah that's the workaround I used. As long as you include the openid scope it returns an id_token without a nonce (as long as you exclude a nonce from your request) that you can then use to signin to supabase. I had some initial issues getting the id_token consistently but that must have been due to me rushing through a bunch of tests and losing track of the changes I was making.

brown62 avatar Feb 05 '25 19:02 brown62

I found out from Supabase's Google auth article that you need to pass in hashed nonce to Azure, and pass non-hashed nonce to Supabase. This solves the nonce mismatch error for me.

// Supabase Auth expects the provider to hash it (SHA-256, hexadecimal representation)
// we need to provide a hashed version to Azure and a non-hashed version to signInWithIdToken.
async function generateNoncePair(): Promise<{ nonce: string; hashedNonce: string }> {
  // Generate a random nonce
  const nonce = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32))));

  // Hash the nonce for Azure authentication
  const encoder = new TextEncoder();
  const encodedNonce = encoder.encode(nonce);
  const hashBuffer = await crypto.subtle.digest('SHA-256', encodedNonce);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashedNonce = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');

  return { nonce, hashedNonce };
import { MsalProvider, useMsal } from '@azure/msal-react';

const { nonce, hashedNonce } = await generateNoncePair();

// Azure auth
const { instance } = useMsal(); 
const result: AuthenticationResult = await instance.loginPopup({
  scopes: ['openid', 'email'],
  prompt: 'select_account',
  nonce: hashedNonce, // PASS IN HASHED NONCE
});

// Supabase auth
const { data } = await throwIfError(
  supabase.auth.signInWithIdToken({
    provider,
    token,
    nonce, // PASS IN UNHASHED NONCE
  }),
);

withlovee avatar Aug 28 '25 22:08 withlovee

@withlovee Following your solution worked.

dev-talha-akbar avatar Sep 01 '25 09:09 dev-talha-akbar