Building a custom React portal for supervisors. We’re using the implicit grant flow to let users log in via the standard Genesys Cloud login screen. The redirect back to our app includes the access_token in the URL fragment.
The problem is validation. I can’t just trust the token is valid on the client side, but we don’t have a backend service to verify the signature against the JWKS endpoint. I tried using react-jwt to decode it, but that only decodes, it doesn’t verify the signature.
I need to verify the token is actually signed by Genesys Cloud before calling our internal APIs. Here is the flow:
- User clicks login, redirects to
https://login.mypurecloud.com/oauth/authorize.
- Genesys redirects back to
https://ourapp.com/callback#access_token=eyJ...&token_type=bearer&expires_in=3600.
- React app grabs the token.
I’m trying to fetch the public keys from https://login.mypurecloud.com/oauth/jwks and verify the token locally using jose library. The code looks like this:
import { jwtVerify, importJWK } from 'jose';
async function validateToken(token) {
const jwks = await fetch('https://login.mypurecloud.com/oauth/jwks').then(r => r.json());
const key = jwks.keys[0]; // Assuming first key works
const publicKey = await importJWK(key, 'RS256');
try {
const { payload } = await jwtVerify(token, publicKey);
return payload;
} catch (err) {
console.error('Token invalid', err);
return null;
}
}
This fails with ERR_JWT_INVALID_SIGNATURE. I suspect the key format or the algorithm mismatch. The token header says alg: RS256. The JWKS endpoint returns RSA keys. Is there a simpler way to validate this in the browser without writing custom crypto logic? Or am I missing a step in importing the JWK? The token works fine when passed to /api/v2/tokens/me, so the token itself is valid. Just can’t prove it client-side.
1 Like
Implicit grant is a security nightmare and honestly, you shouldn’t be doing this in a browser. Storing tokens in localStorage or URL fragments makes them trivially easy to steal via XSS. If you really have no backend, you’re stuck with client-side validation, but it’s fragile.
The only way to verify the token without hitting an introspection endpoint (which requires CORS headers that Genesys doesn’t always play nice with) is to use the token verification endpoint directly. Since Genesys doesn’t expose a public JWKS endpoint for direct client-side signature verification in this context, you have to rely on the API to confirm the token is valid for the current user.
Here’s how I’d handle it using fetch in React. It’s not pretty, but it works.
const TOKEN_VERIFY_URL = 'https://api.mypurecloud.com/api/v2/tokens/me';
async function validateToken(token) {
try {
// Send the token as a Bearer token to verify it
const response = await fetch(TOKEN_VERIFY_URL, {
method: 'HEAD',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('Token is invalid or expired');
}
// If the request succeeds, the token is valid.
// Note: This endpoint returns no body, just a 200 status.
return { valid: true };
} catch (err) {
console.error('Token validation failed', err);
return null;
}
}
The catch is that this approach relies on the API accepting the request, which means you’re dependent on CORS policies allowing HEAD requests with custom headers from your domain. If Genesys blocks this, you’re stuck.
Also, don’t forget to check the exp claim locally if you have the token payload decoded, as this check happens before the network call. If you skip the server-side verification for some reason, you’re wide open.
This whole setup feels wrong though. A tiny Azure Function or AWS Lambda to proxy the auth is way safer. The implicit flow is deprecated for a reason.