mirror of
https://github.com/vacp2p/nim-jwt.git
synced 2026-01-08 03:43:54 -05:00
feat: add toFlattenedJson
This commit is contained in:
50
README.md
50
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
|
||||
|
||||
```
|
||||
|
||||
75
jwt.nim
75
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
|
||||
|
||||
106
tests/t_jwt.nim
106
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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user