Search code examples
node.jsreactjsexpressjwtauth0

How can I verify a token from Auth0 in nodejs backend with jwt? JsonWebTokenError


I have a react frontend with auth0 to log users, once a user is logged in I get the token with getAccessTokenSilently() and send it to the backend like this:

const { user, isAuthenticated, getAccessTokenSilently } = useAuth0()

useEffect(() => {
    if (user) getTickets()
}, [user])

async function getTickets() {
    const token = await getAccessTokenSilently()
    const response = await fetch('http://localhost:4000/api/gettickets', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ user, bar }),
    })
    const data = await response.json()
}

Once I have the token in my backend I try to verify it jsonwebtoken like this:

import express from 'express'
import jwt from 'jsonwebtoken'
import dotenv from 'dotenv'
dotenv.config()

const routerGetTickets = express.Router()

routerGetTickets.post('/', async (req, res) => {
    const PUBKEY = process.env.PUBKEY
    const token = req.headers.authorization?.split(' ')[1]

    if (token && PUBKEY) {
        jwt.verify(token, PUBKEY, { algorithms: ['RS256'] }, (err, data) => {
            console.log('token :>> ', token)
            if (err) {
                res.sendStatus(403)
                console.log('err :>> ', err)
                return
            } else {
                console.log('everything ok')
            }
        })
    }
})
export default routerGetTickets

If I'm not wrong, with algorithm RS256 I have to provide the public key witch I got using openssl with the signin certification I downloaded from my aplication in Auth0 dashboard.

This is the error I get: err :>> JsonWebTokenError: secretOrPublicKey must be an asymmetric key when using RS256

And this is my index.ts:

import React from 'react'
import ReactDOM from 'react-dom/client'
import './sass/index.scss'
import App from './App'
import { BrowserRouter } from 'react-router-dom'
import { Auth0Provider } from '@auth0/auth0-react'

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

root.render(
<React.StrictMode>
    <BrowserRouter basename='/tombola'>
        <Auth0Provider
            domain='*******.uk.auth0.com'
            clientId='*****************************'
            authorizationParams={{
                redirect_uri: `${window.location.origin}/tombola/callback`,
                audience: 'https://******.uk.auth0.com/api/v2/',
                scope: 'read:current_user update:current_user_metadata'

            }}
        >
            <App />
        </Auth0Provider>
    </BrowserRouter>
</React.StrictMode>
)

Solution

  • I had this issue in the verify jwt method also using Auth0, the problem is with the public key.

    You didn't show your .env file where the PUBKEY variable are but I imagine you got it from the Application -> settings -> advanced settings -> certificates, as the Auth0 docs says, but thats the certificate not the pubkey.

    To get the public key you need to use the "jwks-rsa" library, it retrieves the publickey using the certificate, getting it from your Auth0 application domain, and for this it needs two things:

    1- The domain to construct the url that Auth0 serve the keys.

    2- the "kid" header of your jwt token, that identified the correct key.

    The code look like this:

    async function HandleRequest(req, res) {
    const cookie = req.headers.cookie;
    
    const token = cookie.split(";")
    .find(c => c.trim().startsWith("access_token="))
    .split("=")[1]
    
    const kid = decode(token, { complete: true }).header.kid;
    
    const publicKey = (await JwksRsa({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `https://${process.env.DOMAIN}/.well-known/jwks.json`
    }).getSigningKey(kid)).getPublicKey()
    
    if (cookie && cookie.includes("access_token")) {
        verify(
            token,
            publicKey,
            {
                algorithms: ["RS256"],
            },
            (err, decoded) => {
                if (err) {
                    res.write(JSON.stringify(err));
                } else {
                    res.write(JSON.stringify(decoded));
                }
            }
        );
    } else {
        res.write("No access token found")
    }
    res.end();
    return
    }
    

    Note that in the example, I'm in a api route, dealing with a request and getting the jwt token in the headers, my jwt is in the headers as "access_token", yours could be different, then decoding it, extracting the kid identifier, passing it to the jwks-rsa alongside the domain constructed url, and then the jwks-rsa library is dealing with all request to get the certificate from your Auth0 application, and all the cryptography work of retrieving the public key from the certificate.

    You can retrieve from the certificate using "crypto" node module, but I tried and it was a mess because it works bettem with .pem files than with strings, but it's a useful thing to know.

    Hope this helps you, I know that Auth0 can mess with our heads while learning.