-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add JWT functionality or jwt integration docs #6
Comments
Hi @jsmestad, As far as generating a JWT structure, I'm not sure what now = Time.now.to_i # 1503593573
secret = 'some128bitsecret'
# ruby-jwt example
JWT.encode({ iat: now, exp: now + 3600 }, secret, 'HS256')
# => "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MDM1OTcxNzMsImlhdCI6MTUwMzU5MzU3M30._BR5iowyyLA1OzM68ZvI2ex8F7kiz-rXR8hSZAjZETI"
# jose example
jwk = JOSE::JWK.from_oct(secret)
jws = JOSE::JWS.from({ 'alg' => 'HS256' })
jwt = JOSE::JWT.from({ 'iat' => now, 'exp' => now + 3600 })
JOSE::JWT.sign(jwk, jws, jwt).compact
# => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDM1OTcxNzMsImlhdCI6MTUwMzU5MzU3M30.wl57Gl5VJl-dvy4TBs2zjsDJb2IuRj-ue9E_E_tGgqw" Side Note: The protected header and signature are different between the two libraries because I left out restricting much of anything related to the JWT claims themselves because all of the claims defined in RFC 7519 are optional. By comparison, specifications that actually use JWTs typically have the actual claims requirements and validations spelled out (for example, ID Tokens in OpenID Connect Core 1.0). Companies like Google (and other early adopters of OAuth2), have some really weird claims use cases for JWTs that aren't defined in any public specifications. So my thought was to keep this library relaxed on claims generation/validation and allow other libraries to add their own protocol-specific claims generation/validation. However, I like what I have something similar as part of an unreleased Identity and Access Management (IAM) library in Erlang: https://gist.github.com/potatosalad/88e6c10eaad3cbd1d6650b2f9fa32358 You would use the library as follows (in Elixir): secret = "some128bitsecret"
# Access Token generation
token =
:iam_claims.new(%{
claims: %{},
jwt_id: {:hash, :md5},
key_id: true
})
|> :iam_claims.issue(1503593573)
|> :iam_claims.expire_after(3600)
|> :iam_claims.put(:audience, "client_id")
|> :iam_claims.put(:issuer, "https://self-issued.me")
|> :iam_claims.put(:subject, "user_id")
|> :iam_claims.sign("HS256", secret)
# => "eyJhbGciOiJIUzI1NiIsImtpZCI6IkJrWUg0NUZ3ZTNkdzBYbmNXN3IwUXFuR19qbF9VcWp6NlBtMDRUekhhN28iLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJjbGllbnRfaWQiLCJleHAiOjE1MDM1OTcxNzMsImlhdCI6MTUwMzU5MzU3MywiaXNzIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZSIsImp0aSI6IlMtcFN1b3k0eDJzczFheG5tR1dic1EiLCJzdWIiOiJ1c2VyX2lkIn0.MFVG-aho-mAj2fcDKmwHy7iobHKGsLtrLyxco88m9pA"
# Access Token verification & validation
assert =
:iam_assert.new(%{
audience: "client_id",
issuer: "https://self-issued.me",
jwt_id: {:hash, :md5},
now: 1503593573,
required: %{
not_before: false
},
subject: "user_id"
})
|> :iam_assert.require([
:audience,
:expiration_time,
:issued_at,
:issuer,
:subject
])
|> :iam_assert.authenticate(token, ["HS256"], secret)
# => %:iam_assert{assertion: "eyJhbGciOiJIUzI1NiIsImtpZCI6IkJrWUg0NUZ3ZTNkdzBYbmNXN3IwUXFuR19qbF9VcWp6NlBtMDRUekhhN28iLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJjbGllbnRfaWQiLCJleHAiOjE1MDM1OTcxNzMsImlhdCI6MTUwMzU5MzU3MywiaXNzIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZSIsImp0aSI6IlMtcFN1b3k0eDJzczFheG5tR1dic1EiLCJzdWIiOiJ1c2VyX2lkIn0.MFVG-aho-mAj2fcDKmwHy7iobHKGsLtrLyxco88m9pA",
# audience: "client_id", authorized_party: nil, checks: %{},
# claims: %{"aud" => "client_id", "exp" => 1503597173, "iat" => 1503593573,
# "iss" => "https://self-issued.me", "jti" => "S-pSuoy4x2ss1axnmGWbsQ",
# "sub" => "user_id"}, issuer: "https://self-issued.me", jwt_id: {:hash, :md5},
# loaded: true, max_age: nil, not_before: nil, now: 1503593573,
# protected: %{"alg" => "HS256",
# "kid" => "BkYH45Fwe3dw0XncW7r0QqnG_jl_Uqjz6Pm04TzHa7o", "typ" => "JWT"},
# public_key: nil,
# required: %{audience: true, expiration_time: true, issued_at: true,
# issuer: true, not_before: false, subject: true}, subject: "user_id",
# validated: true, verified: true, window: 5} You can play around with the resulting JWT here. The flow of stages for "authentication" of the JWT is different depending on whether the JWT was signed or encrypted:
Here's the description of the different stages:
Is something like that what you're looking for? If so, do you think it should be part of this library or does it belong in a separate library for the more common use cases of JWTs? Opinions and contributions are definitely welcome 😃 |
Ah that makes sense. The missing claims is what threw me off and the fact that def verify!
::JOSE::JWS.verify_strict(public_jwk, ["ES512"], @jwt)
end
def body
JSON.parse(verify![1])
rescue JSON::ParserError
{}
end I also have no clue what the JWT struct and Map are supposed to be used for. I dug into the source and docs but still cannot quite figure it out... |
So, a JWS can actually sign non-JSON data, which is why I don't do JSON parsing with jwk = JOSE::JWK.from_oct('some128bitsecret')
jws = JOSE::JWS.from({ 'alg' => 'HS256' })
plaintext = 'test'
jwt = JOSE::JWT.from({ 'test' => true })
# plain text example
signed_plaintext = JOSE::JWS.sign(jwk, plaintext, jws).compact
# => "eyJhbGciOiJIUzI1NiJ9.dGVzdA.Q6Csqi1yGWpbZZ9ETjq_clf0TEmo2p4RhDy1J6xQyIM"
JOSE::JWS.verify_strict(jwk, ['HS256'], signed_plaintext)
# => [true,
# "test",
# #<struct JOSE::JWS
# alg=#<struct JOSE::JWS::ALG_HMAC hmac=OpenSSL::Digest::SHA256>,
# b64=nil,
# fields=JOSE::Map[]>]
# JWT example
signed_jwt = JOSE::JWT.sign(jwk, jws, jwt).compact
# => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0Ijp0cnVlfQ.wdE-oAmXMPFyhWD0zXYLhR6nCj4ku00WlJnNjsVC0Ck"
JOSE::JWT.verify_strict(jwk, ['HS256'], signed_jwt)
# => => [true,
# #<struct JOSE::JWT fields=JOSE::Map["test" => true]>,
# #<struct JOSE::JWS
# alg=#<struct JOSE::JWS::ALG_HMAC hmac=OpenSSL::Digest::SHA256>,
# b64=nil,
# fields=JOSE::Map["typ" => "JWT"]>] An instance of
|
Also, you can see some of the difference between whether a JSON object is expected with the peek methods in The documentation could definitely be improved, though. I tried to imply the use-case based on the names of the arguments (see how Also, the ordering of arguments has bothered me for a while now because I was all over the place when I wrote the library:
Perhaps having something like |
Ah thats awesome. Yeah I can see the erlang/elixir influence everywhere. If I could say one thing on the project, it would be to add to a GH wiki and just demonstrate how to do things like what I'd be happy to get started if you feel its useful to the project overall. |
Heck yeah, that would be super helpful. You probably have a much better idea/perspective on what's lacking from the initial documentation for newcomers. Even if you put some placeholders in the wiki for areas like "show how you encrypt and decrypt a JWT here" I can go in a flesh those out. |
Thanks for putting this gem together @potatosalad. Seems like this is a really complete JOSE implementation minus generating a JWT structure. Is the point to use something like
ruby-jwt
and to build the JWT header/body structure then pass it to this gem or did I miss something in the docs?Happy to help 😄
The text was updated successfully, but these errors were encountered: