Ceci est une ancienne révision du document !
Table des matières
JSON Web Token
Le JSON Web Token (JWT, prononcé jot) est un standard d'authentification.
http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html
Packages node
- jose et documentation (panva, moins utilisée, plus complet)
- jose (version cisco, plus utilisée, moins complet)
Outils
Spécifications
JWT
JWKS
Structure
Header
Payload
iss | Issuer |
sub | Subject |
exp | Expiration |
Exemple d'ID Token Cognito
Header:
{ "kid": "S/2sYDHJPpXvQcl8tbKE5A+AESbxu/g6JfQWXH7Jj60=", "alg": "RS256" }
Payload
{ "sub": "39d769aa-f1a5-4e47-b6e0-a302f819ba82", "aud": "7diiepl30kilf5fhgk1b2jf7c5", "email_verified": true, "event_id": "d1f41e61-5fdd-4815-a333-c5b86ca9c16e", "token_use": "id", "auth_time": 1595250673, "iss": "https://cognito-idp.ca-central-1.amazonaws.com/ca-central-1_dWajkYc7s", "cognito:username": "johndoe@gmail.com", "exp": 1595254273, "iat": 1595250673, "email": "johndoe@gmail.com" }
eyJraWQiOiJTXC8yc1lESEpQcFh2UWNsOHRiS0U1QStBRVNieHVcL2c2SmZRV1hIN0pqNjA9IiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIzOWQ3NjlhYS1mMWE1LTRlNDctYjZlMC1hMzAyZjgxOWJhODIiLCJhdWQiOiI3ZGlpZXBsMzBraWxmNWZoZ2sxYjJqZjdjNSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJldmVudF9pZCI6ImQxZjQxZTYxLTVmZGQtNDgxNS1hMzMzLWM1Yjg2Y2E5YzE2ZSIsInRva2VuX3VzZSI6ImlkIiwiYXV0aF90aW1lIjoxNTk1MjUwNjczLCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuY2EtY2VudHJhbC0xLmFtYXpvbmF3cy5jb21cL2NhLWNlbnRyYWwtMV9kWFlqa1pjN3IiLCJjb2duaXRvOnVzZXJuYW1lIjoic2dhcmllcHlAYmh2ci5jb20iLCJleHAiOjE1OTUyNTQyNzMsImlhdCI6MTU5NTI1MDY3MywiZW1haWwiOiJzZ2FyaWVweUBiaHZyLmNvbSJ9.UttCWsH9b_9ahYmYx87iyXc60BjqJTrDZQ3e4gHjOPgX97wFdawwCRmJd0ChI6J-dUVogwCWGaj3z2JPDo2XYB8L7WPxFceFkEHLOQTOSU2uk_3B9d-CRTBPjAjllV89dksJtRRmwhUIdT_uBGNpoI9T_CtMMm6yBV9zJCjPrgys1eRdsRk9BA45bd8Saf_JPqUa9lMqvT8Ka9uq0naXt0CB4m6zwMvqbBMVONdbeZ2ET3BZxt2BW9Qp6Bzg4qUe2Mliatfzd0tIyDkWlHY1lZcrJqkFLwmnxLDwtzlYek5q0fzgJwF0qxiuPwk16opuj_plOnlo7CU9-WDzNJszgg
JOSE
Clé privée
Générer la clé privée:
$ openssl genrsa -out rsa-2048.pem 2048
Une clé de longueur 2048 bits semble suffisante jusqu'en 2030. Sinon utiliser 3072 ou 4096.
Avec ssh-keygen:
ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key # Don't add passphrase openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub cat jwtRS256.key cat jwtRS256.key.pub
Créer un JWT
const privateKey = readFileSync('./path/to/rsa-2048.pem'); const rsaKey = JWK.asKey(privateKey); const payload = { 'urn:example:claim': 'foo' }; const token = JWT.sign(payload, rsaKey, { algorithm: 'RS256', audience: ['urn:example:client'], issuer: 'https://op.example.com', expiresIn: '2 hours', header: { typ: 'JWT' } });
Créer le endpoint JWKS
Quand on importe la clé privée RSA, on peut demander la clé publique:
const privateKey = readFileSync('./path/to/rsa-2048.pem'); const rsaKey = JWK.asKey(privateKey); const publicKey = JSON.stringify(rsaKey.toJWK());
Ce qui donne quelque chose de ce genre:
{ "e":"AQAB", "n":"5ColF8Lypyud9iKJjOeaTG7yP-KxdJ...9FMDEBQ", "kty":"RSA", "kid":"O06jlZQ_2JW0UXF6qvbMIFdaXFNnIoKHS9aVbv5-Mvc" }
Et comme on le veut dans un set
(JWKS), on peut faire ceci:
{ "keys": [ { "e":"AQAB", "n":"5ColF8Lypyud9iKJjOeaTG7yP-KxdJ...9FMDEBQ", "kty":"RSA", "kid":"O06jlZQ_2JW0UXF6qvbMIFdaXFNnIoKHS9aVbv5-Mvc" } ] }
On peut ajouter la propriété “alg”: “RS256”,
, mais n'est pas nécessaire pour valider la signature plus tard.
Validation avec JWKS
Pour importer les fonctions nécessaires de jose
:
import { JWK, JWT, JWKS } from 'jose';
On récupère les clés publiques via un endpoint JWKS, normalement sous une URL /.well-known/jwks.json
.
const jwksEndpoint = 'https://c1058d53fc6f.ngrok.io/.well-known/jwks.json'; // Exemple d'URL de JWKS const { data } = await axios.get<JWKSKeys>( jwksEndpoint, ); const jwksKeys = data;
Interface de JWKSKeys:
export interface JWKSKeys { keys: [ { kty: string; e: string; use?: string; kid: string; alg?: string; n: string; } ] }
On transforme le data reçu en clés jose
:
const rsaKeys = jwksKeys.keys.map((key) => JWK.asKey(key)); const keystore = new JWKS.KeyStore(rsaKeys);
Ensuite la validation en donnant le keystore.
let tokenVerification: TokenPayload; try { tokenDecoded = JWT.verify(payload.jwt, keystore) as TokenPayload; } catch (e) { console.error('Token Verification failed.', e); } console.log(tokenDecoded);
Le TokenPayload
dépend de ce qu'on veut mettre dans le payload du JWT, ceci est un exemple:
export interface TokenPayload { name: string; nickname: string; picture: string; email: string; // eslint-disable-next-line camelcase email_verified: boolean; }
jwks-rsa
Exemple d’utilisation de JWKS-RSA.
import * as jwksClient from 'jwks-rsa'; // *** skipped code *** const client = jwksClient({ strictSsl: true, // Default value jwksUri: 'https://7426c678d401.ngrok.io/.well-known/jwks.json', }); const kid = 'id12345'; const agetSigningKey = promisify(client.getSigningKey).bind(client); return agetSigningKey(kid);