So, I’m using Next.js for Auth0 and I’m trying to link accounts using the @auth/auth0-spa-js
library to instantiate a new Auth0Client
to get the linking and then I call the identities
url via POST to link but I get this error:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid token",
"attributes": {
"error": "Invalid token"
}
}
and here’s the URL/payload:
https://MY_WEBSITE.us.auth0.com/api/v2/users/auth0%7C62825f0192ed030070eb920d/identities
"link_with": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllISmV2eEF1T1I4SzJaUmNmLThqNiJ9.eyJnaXZlbl9uYW1lIjoiRGFuIiwiZmFtaWx5X25hbWUiOiJKYXNub3dza2kiLCJuaWNrbmFtZSI6ImQuamFzbm93c2tpIiwibmFtZSI6IkRhbiBKYXNub3dza2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2prcGhpTTdlX1hpYlh4aTJHRk0wWEVyVE0tWDFSZUlCZldqbC1QU0E9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA1LTE2VDE3OjQzOjEzLjg2NFoiLCJlbWFpbCI6ImQuamFzbm93c2tpQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL3ZpbmRleC1kZXYudXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTEwMzEzMTU1NzQ3MTQ1MDUwNjM4IiwiYXVkIjoia1plNnM0SVJXMjhXa1dWdk5ibmNvQlhiSmVtd0tNcUMiLCJpYXQiOjE2NTI3MjI5OTQsImV4cCI6MTY1Mjc1ODk5NCwiYXV0aF90aW1lIjoxNjUyNzIyOTkzLCJub25jZSI6ImFHbDNWMDlwVWs5TmRWVmpZVTExU201K2FITmxRbkZUYmpoRE1pNDVaVXR6Y0VGdmNuWm5RekZsVWc9PSJ9.qc6EU3zCPApDt56fqG0wbEoqh6UlcFOvbYvjIVWPPD3zG-t31aia9gVzqp8RT8oSE_LFE51duPo65QJvB9Z1Ea98A9GH7vUzRYY2nnNBsJ8A-nKCubJjTDYBWQc9Ds3r_Vl3eo0N9gSBqm_ZG2M1VBfoRwP9L5j73xwVtpiLnFZM3-Tl4s495Mzs5mS2x-Q04w6zIFCcoqh_pOv7odYKvV6IrJa96665X5a2UKdbM0XfJYGj5jKsi-Eo4GxHfxi63T571cWH6O9Ky4c49s0xmzHQvibQfpvx3W0e33x8zx5aK9XpYPeeNMOLG7PKDMKikOe_PO21VAIVRVBJFFRO5w"
}
And I know that’s right because I used JWT.IO to verify and it looks good.
And just for good measure, during a0.loginWithPopup
I hit the https://MY_WEBSITE.us.auth0.com/oauth/token
with this payload:
{
"client_id": "CLIENT_ID",
"code_verifier": "1UfVE9x9h................9.oKIUYqfVDi3dI-Hr-pjl",
"code": "j6GyP8uGyfic...................FD-bzOvSjvGD",
"grant_type": "authorization_code",
"redirect_uri": "http://localhost:3000"
}
(removed some code/verifier for brevity…) and this the response:
{
"access_token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly92aW5kZXgtZGV2LnVzLmF1dGgwLmNvbS8ifQ..7Jl1AmIQ5h_1pB_j.tiUeBqpGPTDtxPyL6Qo9_8GpMcAMnowRujX0S5kfLzyaxd1ZqLOBgoHeIZaZ-YTz0_Mh0RMiPJVpggv9E4S3ibuoST-5R1dVtjtr2WolM6XeWkvRMzXo9BrDutDKem2CFkDI8yXzoPxxxE7590LHqLSKd4yyf_CpwS1h_vzL283dxJlWzrMbPhHoWtAoSDw3MLn3JDNrt2mCVYN7c93Cs-XyGz5Xz8aToj9-dF6ybRnoLrJk2NdLPythhkJh1cJKqV0BrdwJf7mByVUA1aA2VAD7FJ24B6mjagX8L_zpuW2WsJ0gD-y5za9BWjjnr5N4048a.QY1Z2dq1suLO5wb4x1zbkQ",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IllISmV2eEF1T1I4SzJaUmNmLThqNiJ9.eyJnaXZlbl9uYW1lIjoiRGFuIiwiZmFtaWx5X25hbWUiOiJKYXNub3dza2kiLCJuaWNrbmFtZSI6ImQuamFzbm93c2tpIiwibmFtZSI6IkRhbiBKYXNub3dza2kiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EtL0FPaDE0R2prcGhpTTdlX1hpYlh4aTJHRk0wWEVyVE0tWDFSZUlCZldqbC1QU0E9czk2LWMiLCJsb2NhbGUiOiJlbiIsInVwZGF0ZWRfYXQiOiIyMDIyLTA1LTE2VDE3OjQzOjEzLjg2NFoiLCJlbWFpbCI6ImQuamFzbm93c2tpQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL3ZpbmRleC1kZXYudXMuYXV0aDAuY29tLyIsInN1YiI6Imdvb2dsZS1vYXV0aDJ8MTEwMzEzMTU1NzQ3MTQ1MDUwNjM4IiwiYXVkIjoia1plNnM0SVJXMjhXa1dWdk5ibmNvQlhiSmVtd0tNcUMiLCJpYXQiOjE2NTI3MjI5OTQsImV4cCI6MTY1Mjc1ODk5NCwiYXV0aF90aW1lIjoxNjUyNzIyOTkzLCJub25jZSI6ImFHbDNWMDlwVWs5TmRWVmpZVTExU201K2FITmxRbkZUYmpoRE1pNDVaVXR6Y0VGdmNuWm5RekZsVWc9PSJ9.qc6EU3zCPApDt56fqG0wbEoqh6UlcFOvbYvjIVWPPD3zG-t31aia9gVzqp8RT8oSE_LFE51duPo65QJvB9Z1Ea98A9GH7vUzRYY2nnNBsJ8A-nKCubJjTDYBWQc9Ds3r_Vl3eo0N9gSBqm_ZG2M1VBfoRwP9L5j73xwVtpiLnFZM3-Tl4s495Mzs5mS2x-Q04w6zIFCcoqh_pOv7odYKvV6IrJa96665X5a2UKdbM0XfJYGj5jKsi-Eo4GxHfxi63T571cWH6O9Ky4c49s0xmzHQvibQfpvx3W0e33x8zx5aK9XpYPeeNMOLG7PKDMKikOe_PO21VAIVRVBJFFRO5w",
"scope": "openid profile email",
"expires_in": 86400,
"token_type": "Bearer"
}
Lastly, this worked on a stricly SPA-js app but now that I’m integrating it into a Next.js app, perhaps I need to do things different? The documentation on this is a bit out of date hence I am asking here.
Here’s my code:
import { withPageAuthRequired, getAccessToken } from "@auth0/nextjs-auth0";
import { Auth0Client } from "@auth0/auth0-spa-js";
import { useState } from "react";
import { Alert, Button, Container } from "@mui/material";
import Layout from "../components/Layout";
import axios from "axios";
export default withPageAuthRequired(function Connections(
props
): React.ReactElement {
const [identities, setIdentities] = useState(null);
async function linkAccount() {
const a0 = new Auth0Client({
domain: "MY_WEBSITE.us.auth0.com",
client_id: "CLIENT_ID",
});
const scope =
"openid profile email update:current_user_identities read:users read:current_user read:user_idp_tokens";
await a0.loginWithPopup({
max_age: 0,
scope,
});
// User to link account with
const authenticatedUser = await a0.getIdTokenClaims();
const email = authenticatedUser?.email;
const email_verified = authenticatedUser?.email_verified;
const targetUserIdToken = authenticatedUser?.__raw;
const { id_token } = await a0.getTokenSilently({
detailedResponse: true,
scope,
});
let responseData = null;
if (!email_verified) {
responseData = "Please verify your email: " + email;
} else if (props.user.sub) {
// Link account with new identity
const response = await fetch(
`https://${"MY_WEBSITE.us.auth0.com"}/api/v2/users/${encodeURIComponent(
props.user.sub
)}/identities`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${id_token}`,
},
body: JSON.stringify({
link_with: targetUserIdToken,
}),
}
);
responseData = await response.json();
setIdentities(responseData.identities);
}
}
return (
<Layout>
<Alert sx={{ mt: 3 }} severity="info">
Here are your connections.
</Alert>
<Button
onClick={linkAccount}
sx={{ my: 2 }}
variant="contained"
color="success"
>
Link Accounts
</Button>
<Container sx={{ mb: 2 }}>{JSON.stringify(identities)}</Container>
</Layout>
);
});