Giter Club home page Giter Club logo

jwts.jl's Introduction

JWTs

Build Status codecov

JSON Web Tokens (JWT) are an open, industry standard RFC 7519 method for representing and transferring claims securely between two parties.

Keys and Key Sets

JWK represents a JWK Key (either for signing or verification). JWK can be either a JWKRSA or JWKSymmetric. A RSA key can represent either the public or private key. Ref: https://datatracker.ietf.org/doc/html/rfc7517

JWKSet holds a set of keys, fetched from a OpenId key URL, each key identified by a key id. The OpenId key URL is usually found in the OpenId configuration (e.g. jwks_uri element in https://accounts.google.com/.well-known/openid-configuration).

To create or verify JWT, using a JWKSet is preferred as it provides mechanism of dealing with key rotation. To refresh a JWKSet, or to load keys for the first time, call the refresh! method on it.

julia> using JWTs

julia> keyset = JWKSet("https://www.googleapis.com/oauth2/v3/certs")
JWKSet 0 keys (https://www.googleapis.com/oauth2/v3/certs)

julia> refresh!(keyset)

julia> keyset
JWKSet 2 keys (https://www.googleapis.com/oauth2/v3/certs)

julia> for (k,v) in keyset.keys
           println("    ", k, " => ", v.key)
       end
    7978a91347261a291bd71dcab4a464be7d279666 => MbedTLS.RSA(Ptr{MbedTLS.mbedtls_rsa_context} @0x0000000001e337e0)
    8aad66bdefc1b43d8db27e65e2e2ef301879d3e8 => MbedTLS.RSA(Ptr{MbedTLS.mbedtls_rsa_context} @0x0000000001d77390)

While symmetric keys for signing can simply be read from a jwk file into a JWKSet, creating a JWKSet for asymmetric key signing needs to be done by the calling code. The process may vary depending on where the private key is stored, but as an example below is a snippet of code that picks up private keys from file corresponding to each key in a jwk file.

keyset = JWKSet(keyset_url)
refresh!(keyset)
signingkeyset = deepcopy(keyset)
for k in keys(signingkeyset.keys)
    signingkeyset.keys[k] = JWKRSA(signingkeyset.keys[k].kind, MbedTLS.parse_keyfile(joinpath(dirname(keyset_url), "$k.private.pem")))
end

Tokens

JWT represents a JSON Web Token containing the payload at the minimum. When signed, it holds the header (with key id and algorithm used) and signature too. The parts are stored in encoded form.

julia> using JSON

julia> using JWTs

julia> payload = JSON.parse("""{
           "iss": "https://auth2.juliacomputing.io/dex",
           "sub": "ChUxjfgsajfurjsjdut0483672kdhgstgy283jssZQ",
           "aud": "example-audience",
           "exp": 1536080651,
           "iat": 1535994251,
           "nonce": "1777777777777aaaaaaaaabbbbbbbbbb",
           "at_hash": "222222-G-JJJJJJJJJJJJJ",
           "email": "[email protected]",
           "email_verified": true,
           "name": "Example User"
       }""");

julia> jwt = JWT(; payload=payload)
eyJuYW1lIjoiRXhhbXBsZSBVc2VyIiwiZXhwIjoxNTM2MDgwNjUxLCJhdWQiOiJleGFtcGxlLWF1ZGllbmNlIiwic3ViIjoiQ2hVeGpmZ3NhamZ1cmpzamR1dDA0ODM2NzJrZGhnc3RneTI4M2pzc1pRIiwiaWF0IjoxNTM1OTk0MjUxLCJpc3MiOiJodHRwczovL2F1dGgyLmp1bGlhY29tcHV0aW5nLmlvL2RleCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiMjIyMjIyLUctSkpKSkpKSkpKSkpKSiIsIm5vbmNlIjoiMTc3Nzc3Nzc3Nzc3N2FhYWFhYWFhYWJiYmJiYmJiYmIiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20ifQ

A JWT can be signed using the sign! method, passing a key set and a key id to sign it with.

julia> issigned(jwt)
false

julia> keyset = JWKSet("file:///my/secret/location/jwkkey.json");

julia> refresh!(keyset)

julia> keyid = first(first(keyset.keys)) # using the first key in the key set
"4Fytp3LfBhriD0eZ-k3aNS042bDiCZXg6bQNJmYoaE"

julia> sign!(jwt, keyset, keyid)

julia> issigned(jwt)
true

julia> jwt # note the additional header and signature
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6IjRGeXRwM0xmQmhyaUQwZVotazNhTlMwNDJiRGlDWlhnNmJRTkptWW9hRSJ9.eyJuYW1lIjoiRXhhbXBsZSBVc2VyIiwiZXhwIjoxNTM2MDgwNjUxLCJhdWQiOiJleGFtcGxlLWF1ZGllbmNlIiwic3ViIjoiQ2hVeGpmZ3NhamZ1cmpzamR1dDA0ODM2NzJrZGhnc3RneTI4M2pzc1pRIiwiaWF0IjoxNTM1OTk0MjUxLCJpc3MiOiJodHRwczovL2F1dGgyLmp1bGlhY29tcHV0aW5nLmlvL2RleCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiMjIyMjIyLUctSkpKSkpKSkpKSkpKSiIsIm5vbmNlIjoiMTc3Nzc3Nzc3Nzc3N2FhYWFhYWFhYWJiYmJiYmJiYmIiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20ifQ.zfq-DT4Ft_MSU34pwFrMaealWGs0j7Ynhs9iKjf5Uf4

The kid method shows the key id used to sign a JWT. This is useful while validating a JWT.

julia> kid(jwt)
"4Fytp3LfBhriD0eZ-k3aNS042bDiCZXg6bQNJmYoaE"

Validation

To validate a JWT against a key, call the validate! method, passing a key set and the key id to use.

The isvalid method can be used to check if a JWT is valid (or has been validated at all). It returns nothing if validation has not been attempted and a Bool indicating validity if it has been validated earlier.

julia> isvalid(jwt2)

julia> validate!(jwt, keyset, keyname)
true

julia> isvalid(jwt)
true

The with_valid_jwt method can be used to Run f with a valid JWT. The validated JWT is passed as an argument to f. If the JWT is invalid, an ArgumentError is thrown.

julia> with_valid_jwt(jwt2, keyset) do valid_jwt
           @info("claims", claims(valid_jwt))
       end
┌ Info: claims
│   claims(valid_jwt) =
│    Dict{String, Any} with 10 entries:"name"           => "Example User""exp"            => 1536080651"aud"            => "example-audience"
..."email"          => "[email protected]"

jwts.jl's People

Contributors

devmotion avatar hhaensel avatar rvignolo-julius avatar tanmaykm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

jwts.jl's Issues

ERROR: Unexpected end of input when using `claims` on README example jwt

I am trying to create a dummy JWT string where claims work.
Using the example from the README unfortunately fails

julia> payload = JSON.parse("""{
           "iss": "https://auth2.juliacomputing.io/dex",
           "sub": "ChUxjfgsajfurjsjdut0483672kdhgstgy283jssZQ",
           "aud": "example-audience",
           "exp": 1536080651,
           "iat": 1535994251,
           "nonce": "1777777777777aaaaaaaaabbbbbbbbbb",
           "at_hash": "222222-G-JJJJJJJJJJJJJ",
           "email": "[email protected]",
           "email_verified": true,
           "name": "Example User"
       }""");

julia> jwt = JWT(; payload=payload)
eyJuYW1lIjoiRXhhbXBsZSBVc2VyIiwiZXhwIjoxNTM2MDgwNjUxLCJhdWQiOiJleGFtcGxlLWF1ZGllbmNlIiwic3ViIjoiQ2hVeGpmZ3NhamZ1cmpzamR1dDA0ODM2NzJrZGhnc3RneTI4M2pzc1pRIiwiaWF0IjoxNTM1OTk0MjUxLCJpc3MiOiJodHRwczovL2F1dGgyLmp1bGlhY29tcHV0aW5nLmlvL2RleCIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdF9oYXNoIjoiMjIyMjIyLUctSkpKSkpKSkpKSkpKSiIsIm5vbmNlIjoiMTc3Nzc3Nzc3Nzc3N2FhYWFhYWFhYWJiYmJiYmJiYmIiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20ifQ
julia> claims(JWT(; jwt = string(jwt)))
ERROR: Unexpected end of input
Line: 0
Around: ......
           ^

Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] _error(message::String, ps::JSON.Parser.MemoryParserState)
   @ JSON.Parser ~/.julia/packages/JSON/93Ea8/src/Parser.jl:140
 [3] byteat
   @ ~/.julia/packages/JSON/93Ea8/src/Parser.jl:49 [inlined]
 [4] parse_value(pc::JSON.Parser.ParserContext{Dict{String, Any}, Int64, true, nothing}, ps::JSON.Parser.MemoryParserState)
   @ JSON.Parser ~/.julia/packages/JSON/93Ea8/src/Parser.jl:160
 [5] parse(str::String; dicttype::Type, inttype::Type{Int64}, allownan::Bool, null::Nothing)
   @ JSON.Parser ~/.julia/packages/JSON/93Ea8/src/Parser.jl:450
 [6] parse
   @ ~/.julia/packages/JSON/93Ea8/src/Parser.jl:443 [inlined]
 [7] decodepart(encoded::String)
   @ JWTs ~/.julia/packages/JWTs/d7lxW/src/JWTs.jl:83
 [8] claims(jwt::JWT)
   @ JWTs ~/.julia/packages/JWTs/d7lxW/src/JWTs.jl:85
 [9] top-level scope
   @ REPL[42]:1

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Remove use of `@assert`s in some areas?

I've noticed some uses of @assert, e.g.

JWTs.jl/src/JWTs.jl

Lines 114 to 117 in 8eeb318

function kid(jwt::JWT)::String
@assert issigned(jwt)
get(decodepart(jwt.header), "kid", nothing)
end

In a future minor version of Julia, @asserts may be disabled by default.

Therefore, I think that most (if not all) uses of @assert in this package should be removed and replaced with regular errors, e.g.

function kid(jwt::JWT)::String 
     issigned(jwt) || throw(ArgumentError("jwt is not signed"))
     get(decodepart(jwt.header), "kid", nothing) 
 end 

Method error when using JWKRSA keys

Hi!

I was trying out the package and find out that I am not able to sign a JWT if I use a RSA key. For example, given this key:

{
    "keys": [
        {
            "kty": "RSA",
            "e": "AQAB",
            "use": "sig",
            "kid": "CxppQyS84boO408w0-piW1952Ptvhm6wfkFuFuOVgHc",
            "alg": "RS256",
            "n": "pQWAjbHrhgnM2i8ekh90F_5I_iOzOgUsd6fNEaT5U_SO9mKCOze4g2gu0klNHdmuBo8w2xZRaFjHtfuYw0Qas5kmdrQeDxiYUOjv_mwtAYXBJurPynC-KLF4-dTTFmvScTtTOvho7lrT6VJtLyNHe7vi45vU8QvqthJw6G68TPTmQKsn7lhfbKCh9jBmaA1O9OXuT2XuWrg8VqqShKcU2P22jigq-N7a2JC4M37OzWsQQNz01-GUhTcQ5RY5ECwKxjzaXl4Oz0dNWLMyBS6THaMtuySWh0al_hkRUaCxqmOm0bBoMGoqoR9eYPdyyy42NpjL1O4Cc2igGhe7W4cLFQ"
        }
    ]
}
payload = Dict(
    "name" => "Ramiro Vignolo",
    "exp" => 1536080651,
    "iat" => 1535994251
)

jwt = JWT(; payload=payload)
keyset = JWKSet("file:///file/path/to/jwk.json");
refresh!(keyset)
keyid = first(first(keyset.keys))
sign!(jwt, keyset, keyid)

returns:

ERROR: MethodError: no method matching sign(::RSA, ::MbedTLS.MDKind, ::Vector{UInt8}, ::Random.MersenneTwister)
Closest candidates are:
  sign(::MbedTLS.PKContext, ::MbedTLS.MDKind, ::Any, ::Any) at /home/user/.julia/packages/MbedTLS/4YY6E/src/pk.jl:97
Stacktrace:
 [1] sign!(jwt::JWT, key::JWKRSA, kid::String)
   @ JWTs ~/.julia/packages/JWTs/rck5r/src/JWTs.jl:148
 [2] sign!(jwt::JWT, keyset::JWKSet, kid::String)
   @ JWTs ~/.julia/packages/JWTs/rck5r/src/JWTs.jl:116
 [3] top-level scope
   @ REPL[43]:1

It works if I change to oct. Does something changed in MbedTLS.jl that broke this kind of keys?

Thank you!

refresh! fails if no alg key is supplied

Currently refresh!() fails if a key does not contain an "alg" key.

According to the JWK specification https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4 the alg key is optional, e.g.if only one algorithm is supported.

This is currently the case for Microsoft Azure AD. Therefore, OpenIDConnect fails to validate a token.

I propose to allow for a default_alg value which is taken when no alg key is provided by the key provider. As "RS256" is the default algorithm for OIDC, I'd propose to default default_alg to this value.

That way, OpenIDConnect.jl would work out of the box for Azure AD.

I've submitted a corresponding PR #17 .

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.