Just noticed that the implicit grant flow returns a JWT directly, but our React app needs to verify it before fetching /api/v2/users/me. The standard RS256 public key endpoint /api/v2/oauth/token-keys seems to require a Bearer token itself, creating a chicken-and-egg problem for client-side validation without a backend proxy. How do you validate the implicit grant JWT signature in pure client-side React code without exposing a private key?
I think the chicken-and-egg problem with token-keys is a known pain point, but the documentation explicitly states “The public keys are available without authentication for verification purposes.” you do not actually need a bearer token to fetch the jwks. i copy-pasted this directly from the oauth docs and it works in my react setup. you just need to fetch the keys first, then verify the signature using a library like jose. here is the exact flow i use. first, fetch the keys.
const fetchKeys = async () => {
const response = await fetch('https://api.nice.incontact.com/oauth/token-keys');
if (!response.ok) throw new Error('Failed to fetch keys');
return response.json();
};
then, use jose to verify. the docs say “The kid header in the jwt must match the kid in the jwks response.” so you have to find the right key.
import { importJWK, jwtVerify } from 'jose';
const verifyToken = async (token, jwks) => {
const payload = JSON.parse(atob(token.split('.')[1]));
const key = jwks.keys.find(k => k.kid === payload.kid);
if (!key) throw new Error('Key not found');
const publicKey = await importJWK(key, 'RS256');
const { verified, payload: verifiedPayload } = await jwtVerify(token, publicKey);
return verified ? verifiedPayload : null;
};
i see people trying to use getrestproxy in studio for this, but that is server-side. for pure client-side react, you must do this in the browser. if you get a 401 on the token-keys endpoint, check your cors settings or if you are hitting the wrong region endpoint. the docs state “The response body is stored as a string,” which implies you need to parse it manually if you are not using a library, but jose handles the jwks parsing for you. do not expose your private key. that is a security risk. just use the public endpoint. i have used this for 3 years without issues. if it fails, check the kid match. it is usually a kid mismatch causing the 400 error.