I get Nonces mismatch error for Azure provider when using supabase.auth.signInWithIdToken()
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
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)
}
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 are you suggesting that using "code" instead would help? Either way thanks for the info :)
@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.
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 Following your solution worked.