0

I would like to decode & verify the IdToken provided by AWS cognito. Simple code that could be used on NodeJs(server) and Browser (the same code).

I tried to use the classic jwt-decode but it has some problems on the browser side due dependencies on crypto lib.

Jaekov Segovia
  • 113
  • 1
  • 7
  • Did you check the libraries at jwt.io? https://jwt.io/libraries Use the "filter" feature there for a quick check by language or platform – cyberbrain Jun 20 '23 at 15:31
  • Have you tried this library? https://github.com/awslabs/aws-jwt-verify – Ricardo Sueiras Jun 20 '23 at 16:04
  • So, I checked Jose is the best fit for this purpose. The support includes NodeJs and browser, it's perfect for SSR and clientSide validations. https://github.com/panva/jose/ – Jaekov Segovia Jun 20 '23 at 19:59

1 Answers1

0

I end up using Jose only, as I mentioned in a comment. I faced some minor issues. Jose offers a built-int method to get JWK, but I wanted to cache the response in-memory by my own means. USER_POOL_ID and REGION are required as env.vars.

I am looking forward to any suggestions.

import axios from 'axios'
import * as jose from 'jose'
import { isNumber } from 'lodash'

export const authCookies = {
    userCookie: 'auth.idToken',
    accessTokenCookie: 'auth._token.local',
    strategy: 'auth.strategy',
    expiration: 'auth._token_expiration.local'
}

const USER_POOL_ID = process.env.USER_POOL_ID
const REGION = process.env.AWS_REGION || 'us-east-1'

const cachedJwks = []

/**
 * @description get cognito JWKS and cache the result. If kid is no found refresh cache.
 * @param {string} currentKid - current kid to double check cache or refresh.
 */
const getJwk = async currentKid => {
    if (currentKid) {
        // console.log('-----> cachedJwks', cachedJwks)
        const cachedKey = cachedJwks.find(item => item.kid === currentKid)
        if (cachedKey) {
            // console.log('-----> cachedKey', cachedKey)
            return cachedKey
        }
        cachedJwks.length = 0

        const { data } = await axios.get(`https://cognito-idp.${REGION}.amazonaws.com/${USER_POOL_ID}/.well-known/jwks.json`)
            .catch(error => {
                console.error('Error on getJwk axios call', error)
                return { data: null }
            })
        if (data?.keys) {
            // console.log('-----> data call')
            const key = data.keys.find(item => item.kid === currentKid)
            if (key) {
                cachedJwks.push(...data.keys)
                return key
            }
        }
    }
    console.error('No Key found to verify the token: ', currentKid)
    return null
}

/**
 * @description check expiration date of token.
 * @param {number} expiration - expiration date in seconds.
 */
export const isExpired = expiration => {
    const currentTime = Math.floor(Date.now() / 1000)
    console.log(' expiration >= currentTime', expiration, currentTime)
    return !isNumber(expiration) || expiration < currentTime
}

/**
 * @description verify token with congnito.
 * @param {string} token - token to verify.
 */
export const verifyToken = async token => {
    // @todo: create singletone on lambda pattern
    try {
        // get header using jose get protected header method.
        const header = jose.decodeProtectedHeader(token)

        const jwk = await getJwk(header.kid)

        if (jwk) {
            const pem = await jose.importJWK(jwk, 'RS256')
            const { payload: verifiedBufferData } = await jose.compactVerify(token, pem)
            const verifiedData = JSON.parse(Buffer.from(verifiedBufferData).toString('utf8'))

            if (isExpired(verifiedData.exp)) {
                console.error('Token expired')
                return null
            }

            return verifiedData
        }
    } catch (error) {
        console.error('Error on verifyToken', error)
    }
    return null
}
Jaekov Segovia
  • 113
  • 1
  • 7