From 0a59984730c87f703f3c5b2e833b8b6a1b45aedc Mon Sep 17 00:00:00 2001 From: Gabriel Cruz Date: Tue, 15 Apr 2025 15:36:00 -0300 Subject: [PATCH] feat: add toFlattenedJson --- README.md | 50 ++++++++++++++++++++++- jwt.nim | 75 ++++++++++++++++++++++++++-------- tests/t_jwt.nim | 106 ++++++++++++++++++++++++++++++++++-------------- 3 files changed, 183 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 8637161..c88efbf 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This is a implementation of JSON Web Tokens for Nim, it allows for the following After installing nim's package manager `nimble` execute this: `nimble install jwt` -## Example +## Examples An example to demonstrate use with a userId @@ -90,3 +90,51 @@ proc request(url: string, body: string): string = let resp = request("https://www.googleapis.com/oauth2/v4/token", postdata).parseJson() echo "Access token is: ", resp["access_token"].str ``` + +Registering in [Let's Encrypt's](https://letsencrypt.org/) [ACME](https://www.rfc-editor.org/rfc/rfc8555) server +```nim +let key = "your_rsa_key_here" +let registerAccountPayload = %*{"termsOfServiceAgreed": true} +let resp = makeSignedAcmeRequest(getDirectory()["newAccount"].getStr, registerAccountPayload, key, needsJwk = true) +echo resp.body + +proc makeSignedAcmeRequest( + url: string, payload: JsonNode, accountKey: string, needsJwk: bool = false +): Response = + let key = loadRsaKey(accountKey) + var token = toJWT( + %*{ + "header": + getAcmeHeader(url, needsJwk, base64UrlEncode(key.n), base64UrlEncode(key.e)), + "claims": payload, + } + ) + token.sign(accountKey) + + var client = newHttpClient() + let body = token.toFlattennedJson + echo body + client.request(url, httpMethod = HttpPost, body = $body, headers = newHttpHeaders({"Content-Type": "application/jose+json"})) + +proc getAcmeHeader( + url: string, needsJwk: bool, n: string = "", e: string = "" +): JsonNode = + var header = %*{"alg": Alg, "typ": "JWT", "nonce": getNewNonce(), "url": url} + if needsJwk: + header["jwk"] = %*{"kty": "RSA", "n": n, "e": e} + else: + header["kid"] = "some_kid" + return header + +proc getNewNonce(): string = + let client = newHttpClient() + let nonceURL = getDirectory()["newNonce"].getStr + let resp = client.request(nonceURL, httpMethod = HttpGet) + return resp.headers["replay-nonce"] + +proc getDirectory(): JsonNode = + let client = newHttpClient() + let directory = parseJson(client.get(LetsEncryptURL & "/directory").body) + return directory + +``` diff --git a/jwt.nim b/jwt.nim index adc644f..bbca7d8 100644 --- a/jwt.nim +++ b/jwt.nim @@ -24,13 +24,15 @@ proc splitToken(s: string): seq[string] = raise newException(InvalidToken, "Invalid token") result = parts -proc initJWT*(header: JsonNode, claims: TableRef[string, Claim], signature: seq[byte] = @[]): JWT = +proc initJWT*( + header: JsonNode, claims: TableRef[string, Claim], signature: seq[byte] = @[] +): JWT = JWT( headerB64: header.toBase64, claimsB64: claims.toBase64, header: header, claims: claims, - signature: signature + signature: signature, ) # Load up a b64url string to JWT @@ -48,7 +50,7 @@ proc toJWT*(s: string): JWT = claimsB64: claimsB64, header: headerJson.toHeader(), claims: claimsJson.toClaims(), - signature: signature + signature: signature, ) proc toJWT*(node: JsonNode): JWT = @@ -66,7 +68,9 @@ proc parsed*(token: JWT): string = result = token.header.toBase64 & "." & token.claims.toBase64 # Signs a string with a secret -proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm = HS256): seq[byte] = +proc signString*( + toSign: string, secret: string, algorithm: SignatureAlgorithm = HS256 +): seq[byte] = template hsSign(meth: typed): seq[byte] = crypto.bearHMAC(addr meth, secret, toSign) @@ -75,7 +79,7 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm = template ecSign(hc: typed): seq[byte] = crypto.bearSignECPem(toSign, secret, addr hc) - + case algorithm of HS256: return hsSign(sha256Vtable) @@ -102,25 +106,34 @@ proc signString*(toSign: string, secret: string, algorithm: SignatureAlgorithm = raise newException(UnsupportedAlgorithm, $algorithm & " isn't supported") # Verify that the token is not tampered with -proc verifySignature*(data: string, signature: seq[byte], secret: string, - alg: SignatureAlgorithm): bool = +proc verifySignature*( + data: string, signature: seq[byte], secret: string, alg: SignatureAlgorithm +): bool = case alg of HS256, HS384, HS512: let dataSignature = signString(data, secret, alg) result = dataSignature == signature of RS256: - result = crypto.bearVerifyRSPem(data, secret, signature, addr sha256Vtable, HASH_OID_SHA256, sha256SIZE) + result = crypto.bearVerifyRSPem( + data, secret, signature, addr sha256Vtable, HASH_OID_SHA256, sha256SIZE + ) of RS384: - result = crypto.bearVerifyRSPem(data, secret, signature, addr sha384Vtable, HASH_OID_SHA384, sha384SIZE) + result = crypto.bearVerifyRSPem( + data, secret, signature, addr sha384Vtable, HASH_OID_SHA384, sha384SIZE + ) of RS512: - result = crypto.bearVerifyRSPem(data, secret, signature, addr sha512Vtable, HASH_OID_SHA512, sha512SIZE) + result = crypto.bearVerifyRSPem( + data, secret, signature, addr sha512Vtable, HASH_OID_SHA512, sha512SIZE + ) of ES256: - result = crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, sha256SIZE) + result = + crypto.bearVerifyECPem(data, secret, signature, addr sha256Vtable, sha256SIZE) of ES384: - result = crypto.bearVerifyECPem(data, secret, signature, addr sha384Vtable, sha384SIZE) + result = + crypto.bearVerifyECPem(data, secret, signature, addr sha384Vtable, sha384SIZE) of ES512: - result = crypto.bearVerifyECPem(data, secret, signature, addr sha512Vtable, sha512SIZE) - + result = + crypto.bearVerifyECPem(data, secret, signature, addr sha512Vtable, sha512SIZE) else: assert(false, "Not implemented") @@ -130,16 +143,44 @@ proc sign*(token: var JWT, secret: string) = # Verify a token typically an incoming request proc verify*(token: JWT, secret: string, alg: SignatureAlgorithm): bool = - token.header.alg == alg and verifySignature(token.loaded, token.signature, secret, alg) + token.header.alg == alg and verifySignature( + token.loaded, token.signature, secret, alg + ) proc toString*(token: JWT): string = token.header.toBase64 & "." & token.claims.toBase64 & "." & token.signatureToB64 +proc toFlattenedJson*(token: JWT, unprotectedHeader: bool = false): JsonNode = + # Converts JWT to flattened JSON format + # https://datatracker.ietf.org/doc/html/rfc7515#section-7.2.2 + + if not unprotectedHeader: + return + %*{ + "payload": token.claims.toBase64, + "protected": token.header.toBase64, + "signature": token.signatureToB64, + } + + # protected field is the base64url of "typ" and "alg" + let protected = %*{"typ": token.header["typ"], "alg": token.header["alg"]} + + # (unprotected) header field contains all other header keys and not on base64url + var header = %*{} + for key in token.header.keys: + if not protected.hasKey(key): + header[key] = token.header[key] + return + %*{ + "payload": token.claims.toBase64, + "protected": protected.toBase64, + "header": header, + "signature": token.signatureToB64, + } proc `$`*(token: JWT): string = token.toString - proc `%`*(token: JWT): JsonNode = let s = $token %s @@ -153,7 +194,7 @@ proc verifyTimeClaims*(token: JWT) = if token.claims.hasKey("exp"): let exp = token.claims["exp"].getClaimTime - if now > exp : + if now > exp: raise newException(InvalidToken, "Token is expired") # Verify token nbf exp diff --git a/tests/t_jwt.nim b/tests/t_jwt.nim index 08552aa..a332e08 100644 --- a/tests/t_jwt.nim +++ b/tests/t_jwt.nim @@ -10,10 +10,8 @@ proc getToken(claims: JsonNode = newJObject(), header: JsonNode = newJObject()): initJWT(header.toHeader, claims.toClaims) proc tokenWithAlg(alg: string): JWT = - let header = %*{ "typ": "JWT", "alg": alg } - let claims = %*{ "sub": "1234567890", - "name": "John Doe", - "iat": 1516239022 } + let header = %*{"typ": "JWT", "alg": alg} + let claims = %*{"sub": "1234567890", "name": "John Doe", "iat": 1516239022} initJWT(header.toHeader, claims.toClaims) proc signedHSToken(alg: string): JWT = @@ -21,7 +19,8 @@ proc signedHSToken(alg: string): JWT = result.sign("your-256-secret") const - rsPrivateKey = """-----BEGIN RSA PRIVATE KEY----- + rsPrivateKey = + """-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi @@ -48,7 +47,8 @@ NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE= -----END RSA PRIVATE KEY-----""" - rsPublicKey = """-----BEGIN PUBLIC KEY----- + rsPublicKey = + """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy @@ -57,28 +57,33 @@ e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9 MwIDAQAB -----END PUBLIC KEY-----""" - ec256PrivKey = """-----BEGIN PRIVATE KEY----- + ec256PrivKey = + """-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r 1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G -----END PRIVATE KEY-----""" - ec256PubKey = """-----BEGIN PUBLIC KEY----- + ec256PubKey = + """-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg== -----END PUBLIC KEY-----""" - ec384PrivKey = """-----BEGIN EC PRIVATE KEY----- + ec384PrivKey = + """-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDCAHpFQ62QnGCEvYh/pE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhske enT+rAyyPhGgBwYFK4EEACKhZANiAAQLW5ZJePZzMIPAxMtZXkEWbDF0zo9f2n4+ T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw8lE5IPUWpgu553SteKigiKLU PeNpbqmYZUkWGh3MLfVzLmx85ii2vMU= -----END EC PRIVATE KEY-----""" - ec384PubKey = """-----BEGIN PUBLIC KEY----- + ec384PubKey = + """-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+ Pk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii 1D3jaW6pmGVJFhodzC31cy5sfOYotrzF -----END PUBLIC KEY-----""" - ec512PrivKey = """-----BEGIN EC PRIVATE KEY----- + ec512PrivKey = + """-----BEGIN EC PRIVATE KEY----- MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga 9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf Z6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN @@ -86,14 +91,14 @@ v3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear jMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12 ew== -----END EC PRIVATE KEY-----""" - ec512PubKey = """-----BEGIN PUBLIC KEY----- + ec512PubKey = + """-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47 6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM Al8G7CqwoJOsW7Kddns= -----END PUBLIC KEY-----""" - proc signedRSToken(alg: string): JWT = result = tokenWithAlg(alg) result.sign(rsPrivateKey) @@ -132,16 +137,22 @@ suite "Token tests": test "HS Signature": # Checked with https://jwt.io/ check: - $signedHSToken("HS256") == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.sBnEuqpBDTh4Q9wnxfhWKHPbbspoz-qPNxXqVSS7ZYE" - $signedHSToken("HS384") == "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.e-lF0-wO2pi5y5fCOHPFLTuHqm2hR1LIX3gaCz0xI_Nvw-KPNIpkKVcbxWl2pPz8" - $signedHSToken("HS512") == "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.oAFx4658Y0Bbjko7Vm-X1AUd4XRjvnuznZk8cihzDuIRSZQjXnveoKuj8PIkAWviz-5c--R1HSyM6HZuONtrLQ" + $signedHSToken("HS256") == + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.sBnEuqpBDTh4Q9wnxfhWKHPbbspoz-qPNxXqVSS7ZYE" + $signedHSToken("HS384") == + "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.e-lF0-wO2pi5y5fCOHPFLTuHqm2hR1LIX3gaCz0xI_Nvw-KPNIpkKVcbxWl2pPz8" + $signedHSToken("HS512") == + "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.oAFx4658Y0Bbjko7Vm-X1AUd4XRjvnuznZk8cihzDuIRSZQjXnveoKuj8PIkAWviz-5c--R1HSyM6HZuONtrLQ" test "RS Signature": # Checked with https://jwt.io/ check: - $signedRSToken("RS256") == "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.O2LIRo2GPEVHQCG3nHGvvY89__LgKLPo9EYXLDzH3oQnh_hZvlk350htpqaNMowOlxYGdM77oLsdHVxzFdto9c1pCH0jBG-HXzIKm131QxsZzCyO8ovW_2i6PGeNvsiaggrkdmOKcWcyMksasJcuqIf0h_fWhiK4wdq41Ls8ujLJpQBF3XNzOPt90so7XEvkY0zDVS0N3Bi6Hz5cN101FJFyMcDnq_3QSGMWPy829vC8PT8C0WCBIs7VdK9tEwIvpDENhRRj6cxhUqLCC0ALoynZYBeMcvOWQcz-LqbWuQGvuH2HGsN9zCpbaTdkiupNX__DKG0HUijnesYn1DkY2g" - $signedRSToken("RS384") == "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.OGwjm7YvGCh4gpIuFnM7K88_dEeiSAWzpR0dXzhsne1IPygnXRKoTCdmhA2a01Mj_cW6tWhufSGcuu-7vmdm5Hi8hoDe5Q92kmM44oWikKptCy_zIM_Roe30TPjXxweE_WjV1fMZaAX6UFumikrtWCTcb9rLnSjpHYFgo-buS7cBXg_nK7xgOPz-bQvv8edVWsBWPf92B9Mak-LNZla_F5EAOjXrN16ZQ1y4qE94ro051kryqUddfVonmLSjCrCavttfBMugYf-SCbLp0w_QLaT9gA_bMXVzqyLnIj74Sr_JCWAxcYU5RaFmqZLEpowyp-m9XGdBwVS2118K0TooZg" - $signedRSToken("RS512") == "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.dgb3ak_0nwQyLa2Ssmq3Jok-pr9QfVFnw_63YlFXTkq_V8r816VeCOzBRYvVv6ONvKGZDR_3SAqf3UJp1XkXtN-VyJ7VRoSHZ0d0-3DPArxDrIu20uvoQrbm4LqQtwbGPH-B-Z-7Bvfng-iwhOt1S717AepZsgVjQz2gOvBvzFsg_BDZ6nhU-5GOnIRkJ2amUt5N1TXbzKHkNLtMpKlq1BZbdv_xKSHgw_IHQRl9lIIQs_2_NuTgk8nQQiwtb9L1v3Y3KYpYGCBvgohWDcpyUKOv5f2EHekDpj1f_ALltd8gzWhIDgwK5VbBo8JAkLWDRfeTOS0fh0Faenfn551wqA" + $signedRSToken("RS256") == + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.O2LIRo2GPEVHQCG3nHGvvY89__LgKLPo9EYXLDzH3oQnh_hZvlk350htpqaNMowOlxYGdM77oLsdHVxzFdto9c1pCH0jBG-HXzIKm131QxsZzCyO8ovW_2i6PGeNvsiaggrkdmOKcWcyMksasJcuqIf0h_fWhiK4wdq41Ls8ujLJpQBF3XNzOPt90so7XEvkY0zDVS0N3Bi6Hz5cN101FJFyMcDnq_3QSGMWPy829vC8PT8C0WCBIs7VdK9tEwIvpDENhRRj6cxhUqLCC0ALoynZYBeMcvOWQcz-LqbWuQGvuH2HGsN9zCpbaTdkiupNX__DKG0HUijnesYn1DkY2g" + $signedRSToken("RS384") == + "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.OGwjm7YvGCh4gpIuFnM7K88_dEeiSAWzpR0dXzhsne1IPygnXRKoTCdmhA2a01Mj_cW6tWhufSGcuu-7vmdm5Hi8hoDe5Q92kmM44oWikKptCy_zIM_Roe30TPjXxweE_WjV1fMZaAX6UFumikrtWCTcb9rLnSjpHYFgo-buS7cBXg_nK7xgOPz-bQvv8edVWsBWPf92B9Mak-LNZla_F5EAOjXrN16ZQ1y4qE94ro051kryqUddfVonmLSjCrCavttfBMugYf-SCbLp0w_QLaT9gA_bMXVzqyLnIj74Sr_JCWAxcYU5RaFmqZLEpowyp-m9XGdBwVS2118K0TooZg" + $signedRSToken("RS512") == + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ.dgb3ak_0nwQyLa2Ssmq3Jok-pr9QfVFnw_63YlFXTkq_V8r816VeCOzBRYvVv6ONvKGZDR_3SAqf3UJp1XkXtN-VyJ7VRoSHZ0d0-3DPArxDrIu20uvoQrbm4LqQtwbGPH-B-Z-7Bvfng-iwhOt1S717AepZsgVjQz2gOvBvzFsg_BDZ6nhU-5GOnIRkJ2amUt5N1TXbzKHkNLtMpKlq1BZbdv_xKSHgw_IHQRl9lIIQs_2_NuTgk8nQQiwtb9L1v3Y3KYpYGCBvgohWDcpyUKOv5f2EHekDpj1f_ALltd8gzWhIDgwK5VbBo8JAkLWDRfeTOS0fh0Faenfn551wqA" signedRSToken("RS256").verify(rsPublicKey, RS256) signedRSToken("RS384").verify(rsPublicKey, RS384) @@ -150,8 +161,10 @@ suite "Token tests": test "EC Signature": # Checked with https://jwt.io/ check: - signedECToken("ES256", ec256PrivKey).header.toBase64 == "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9" - signedECToken("ES256", ec256PrivKey).claims.toBase64 == "eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ" + signedECToken("ES256", ec256PrivKey).header.toBase64 == + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9" + signedECToken("ES256", ec256PrivKey).claims.toBase64 == + "eyJuYW1lIjoiSm9obiBEb2UiLCJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNTE2MjM5MDIyfQ" # We don't check signatures, as for ES* algorithms they are random @@ -160,17 +173,50 @@ suite "Token tests": signedECToken("ES512", ec512PrivKey).verify(ec512PubKey, ES512) test "header values": - var token = toJWT(%*{ - "header": { - "alg": "HS256", - "kid": "something", - "typ": "JWT" - }, - "claims": { - "userId": 1 + var token = toJWT( + %*{ + "header": {"alg": "HS256", "kid": "something", "typ": "JWT"}, + "claims": {"userId": 1}, } - }) + ) token.sign(rsPrivateKey) let signed = $token let decoded = signed.toJWT() check decoded.header["kid"].getStr() == "something" + + test "toFlaflattenedJson": + var token = toJWT( + %*{ + "header": {"alg": "HS256", "kid": "something", "typ": "JWT"}, + "claims": {"userId": 1}, + } + ) + token.sign(rsPrivateKey) + let expectedFlattened = + %*{ + "payload": "eyJ1c2VySWQiOjF9", + "protected": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbWV0aGluZyJ9", + "signature": "JlHgw86VQ7xgOn1ACnwqjXfU28CHD_9GrCMu9JO0rr4", + } + + check expectedFlattened == token.toFlattenedJson + + test "toFlaflattenedJson with unprotectedHeader": + var token = toJWT( + %*{ + "header": {"alg": "HS256", "kid": "something", "typ": "JWT"}, + "claims": {"userId": 1}, + } + ) + token.sign(rsPrivateKey) + let expectedFlattenedUnprotected = + %*{ + "payload": "eyJ1c2VySWQiOjF9", + "protected": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9", + "header": {"kid": "something"}, + "signature": "JlHgw86VQ7xgOn1ACnwqjXfU28CHD_9GrCMu9JO0rr4", + } + + check expectedFlattenedUnprotected == token.toFlattenedJson( + unprotectedHeader = true + )