APIs

Validação do Token (para fornecedores)

A chave pública é utilizada para a validação da assinatura de tokens JWT. Para tanto, você precisará acessar os metadados do servidor de autenticação. Os metadados fornecem informações sobre o servidor de autenticação, incluindo a chave pública usada para assinar os tokens. Aqui estão os passos gerais para obter a chave pública do servidor de autenticação.

1) Acesse a URL para obter a chave pública :

A chave pública pode ser obtida neste link.

2) Obtenha informações do JWK Set:

Ao acessar a URL acima, você receberá um documento JSON que contém várias informações, incluindo um JWK Set (JSON Web Key Set) que contém as chaves públicas utilizadas para assinar tokens. Exemplo de JWK Set:

{"keys":
       [
         {"kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
          "use":"enc",
          "kid":"1"},

         {"kty":"RSA",
          "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
     4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
     tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
     QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
     SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
     w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
          "e":"AQAB",
          "alg":"RS256",
          "kid":"2011-04-29"}
       ]
     }

3) Encontre a chave pública:

No JWK Set, você encontrará uma lista de chaves públicas (kid). Procure a chave pública que corresponde ao algoritmo de assinatura utilizado nos tokens que você deseja validar. O kid está indicado no cabeçalho dos tokens que você recebe.

4) Validações:

Ao decodificar o token JWT, deve-se realizar a validação do:

Pode ser que a biblioteca JWT que você utilize, não decodifique a assinatura do token, para que a comparação do valor de "n" seja possível. Nesse caso, para decodificar a assinatura do token e realizar a comparar o valor de "n" você precisará dos valores de "kid", "iss", "alg" e "x5c". Segue abaixo um exemplo de classe.

import { Request, Response, NextFunction } from "express"
import moment from "moment"
const jwt = require("jsonwebtoken")
const fs = require("fs")
const path = require('path')

import { StatusCodes } from "http-status-codes"
import axios, { AxiosPromise } from "axios"

export default abstract class BaseController {

    private latestCertsUpdate:Date|null = null

    async autenticar(req: Request, res: Response, next: NextFunction) {
        try {
            const token = req.headers?.client_token
            if (!token) {
                throw new Error("Token de autorização não informado")
            }

            const decoded = jwt.decode(token, { complete: true })
            if (!decoded) {
                throw new Error("Token de autorização inválido")
            }

            if (moment.unix(decoded.payload.exp) < moment()) {
                throw new Error("Token de autorização expirado")
            }

            await this.validate(decoded, token)

            next()
        } catch (e: any) {
            res.status(StatusCodes.UNAUTHORIZED).json({
                sucesso: [],
                success: [],
                error: [{
                    cod_status: StatusCodes.UNAUTHORIZED.toString(),
                    mensagem: e.message
                }]
            })
        }
    }

    private async validate(decoded:any, token:any):Promise<void> {
        let signingKey = await this.findKey(decoded)
        if (!signingKey) {
            if (this.canUpdateCerts()) {
                await this.updateCerts()

                return await this.validate(decoded, token)
            }

            throw new Error("Token de autorização inválido")
        }

        let signingCerts = signingKey.x5c.map(this.convertCertificateToBeOpenSSLCompatible)
        if (!this.verifyUsingSigningCert(token, signingCerts, signingKey, decoded.payload.iss)) {
            throw new Error("Token de autorização inválido")
        }
    }

    private async findKey(token:any) {
        let certs = await this.getCerts()

        return certs.keys.find((cert: any) => cert.kid === token.header.kid )
    }

    private verifyUsingSigningCert(token:any, signingCerts:any, signingKey:any, issuer:any) {
        for (let i = 0; i < signingCerts.length; i++) {
            try {
                let currSigningCert = signingCerts[i]
                let decodedToken = jwt.verify(
                    token,
                    currSigningCert,
                    {
                        complete: true,
                        algorithms: [signingKey.alg],
                        issuer,
                    }
                )

                return decodedToken
            } catch (e) {
                // do nothing
            }
        }

        return false
    }

    private async getCerts() {
        let certs = await this.getCertsFromDisk()

        !certs?.keys?.length && (certs = await this.updateCerts())
        if (!certs?.keys?.length) {
            throw new Error("Certificado de autorização não encontrado")
        }

        return certs
    }

    private async getCertsFromDisk() {
        const filename = path.resolve(__dirname, '../../public/data/nstechCerts.json')
        if (!fs.existsSync(filename)) {
            return null
        }

        const certs = fs.readFileSync(filename, 'utf8')

        return (certs && JSON.parse(certs)) || null
    }

    private async updateCerts() {
        const response = await this.getCertsFromUrl()
        await this.updateCertsOnDisk(response.data)

        this.latestCertsUpdate = new Date()

        return response.data
    }

    private async getCertsFromUrl():AxiosPromise<any> {
        return axios('https://hub.nstech.com.br/auth/realms/platform/protocol/openid-connect/certs')
    }

    private async updateCertsOnDisk(data:any) {
        fs.writeFileSync(path.resolve(__dirname, '../../public/data/nstechCerts.json'), JSON.stringify(data))
    }

    private canUpdateCerts() {
        return !this.latestCertsUpdate || ((this.latestCertsUpdate.getTime() - (new Date()).getTime()) > 1000 * 60)
    }

    private convertCertificateToBeOpenSSLCompatible(cert: string): string {
        let beginCert = "-----BEGIN CERTIFICATE-----"
        let endCert = "-----END CERTIFICATE-----"
        cert = cert.replace("\n", "")
        cert = cert.replace(beginCert, "")
        cert = cert.replace(endCert, "")
        let result = beginCert
        while (cert.length > 0) {
            if (cert.length > 64) {
                result += "\n" + cert.substring(0, 64)
                cert = cert.substring(64, cert.length)
            } else {
                result += "\n" + cert
                cert = ""
            }
        }
        if (result[result.length ] !== "\n") {
            result += "\n"
        }
        result += endCert + "\n"

        return result
    }
}

🚧

Atenção:

A implementação exata pode variar de acordo com a linguagem de programação, o framework e as bibliotecas que você está usando. Verifique a documentação da biblioteca de JWT que você está usando para entender como carregar e usar a chave pública para validação. Certifique-se de seguir as melhores práticas de segurança ao implementar esse processo em seu aplicativo.