A simple middleware to authenticate users using JWT (JSON Web Tokens) currently, only RS256 is supported.
- RS256 signing
- uses IANA “JSON Web Token Claims”
- JWT lifetime & Expiration support
- custom additional validation through user-provided functions
- custom revocation check through user-provided functions
If you do not want to use a log middleware:
(defn my-handler
[request]
,,,)
(def jwt-middleware (wrap-jwt-auth-fn {:pubkey-path jwt-cert-path}))
(jwt-middleware my-handler)
If you want to use a log middleware that will log both user identites derived from JWT and response statuses (and other response stats):
(defn my-handler
[request]
,,,)
(defn wrap-logs
"A middleware logging the requests"
[handler]
(fn [request]
(let [user-identity (:identity request)
response (handler request)]
(log/info (pr-str {:user-identity user-identity
:uri (:uri request)
:status (:status response)}))
response)))
(def jwt-middleware
(wrap-jwt-auth-with-in-between-middleware-fn
{:pubkey-path jwt-cert-path}
wrap-logs))
(jwt-middleware my-handler)
For Authentication only, and handle the authorization entirely yourself:
(defn my-handler
[request]
,,,)
(let [wrap-authentication
(mk-wrap-authentication {:pubkey-path jwt-cert-path})]
(wrap-authentication my-handler))
At this step the request
passed to my-handler
will have some of the following keys added:
jwt
=> the claims of the JWTidentity
=> the object representing the user identity constructed using JWT claimsjwt-error
=> will contain an error object if the something went wrong with the JWT
The wrap-authentication
will not take any decision about authorization access.
This lib also provides another helper to build another middleware handling
authorization.
You can inject your own authorization rules, via:
(let [wrap-authentication (mk-wrap-authentication
{:pubkey-path "/etc/secret/jwt.pub"
:is-revoked-fn my-revocation-check-fn
:jwt-check-fn my-jwt-checks})
wrap-authorization (mk-wrap-authorization
{:error-handler my-error-handler})]
(wrap-authentication (wrap-authorization my-handler)))
Notice you could add the following keys in the configuration passed to mk-wrap-authentication
, mk-wrap-authorization
and wrap-jwt-auth-fn:
(s/defschema Config
"Initialized internal Configuration"
(st/merge
{:allow-unauthenticated-access?
(describe s/Bool
"Set this to true to allow unauthenticated requests")
:current-epoch
(describe (s/=> s/Num)
"A function returning the current time in epoch format")
:is-revoked-fn
(describe (s/=> s/Bool JWTClaims)
"A function that take a JWT and return true if it is revoked")
:jwt-max-lifetime-in-sec
(describe s/Num
"Maximal number of second a JWT does not expires")
:post-jwt-format-fn
(describe (s/=> s/Any JWTClaims)
"A function taking the JWT claims and building an Identity object suitable for your needs")
:error-handler
(describe (s/=> s/Any)
"A function that given a JWTError returns a ring response.")
:default-allowed-clock-skew-in-seconds
(describe s/Num
"When the JWT does not contain any nbf claim, the number of seconds to remove from iat claim. Default 60.")}
(st/optional-keys
{:pubkey-fn (describe (s/=> s/Any s/Str)
"A function returning a public key (takes precedence over pubkey-path)")
:pubkey-fn-arg-fn (describe (s/=> s/Any s/Any)
"A function that will be applied to the argument (the raw JWT) of `pubkey-fn`")
:post-jwt-format-fn-arg-fn (describe (s/=> s/Any s/Any)
"A function that will be applied to the argument (the raw JWT) of `post-jwt-format-fn`")
:pubkey-path (describe s/Str
"The path to find the public key that will be used to check the JWT signature")
:jwt-check-fn
(describe (s/=> s/Bool JWT JWTClaims)
(str "A function that take a JWT, claims and return a sequence of string containing errors."
"The check is considered successful if this function returns nil, or a sequence containing only nil values."))})))
By default if no JWT authorization header is found the request is terminated with
unauthorized
HTTP response.
By default the :identity
contains the "sub"
field of the JWT. But you can
use more complex transformation. For example, there is a jwt->oauth-ids
function in the code that could be used to handle JWT generated from an OAuth2
provider.
Currently this middleware only supports JWT using claims registered in the IANA “JSON Web Token Claims”,
which means you need to generate JWT using most of the claims described here: https://tools.ietf.org/html/rfc7519#section-4
namely jti
, exp
, iat
, nbf
, sub
:
Claim | Description | Format |
---|---|---|
:exp | Expiration time: https://tools.ietf.org/html/rfc7519#section-4.1.4 | Long |
:iat | Issued At: https://tools.ietf.org/html/rfc7519#section-4.1.6 | Long |
:jti | JWT ID: https://tools.ietf.org/html/rfc7519#section-4.1.7 | String |
:nbf | Not Before: https://tools.ietf.org/html/rfc7519#section-4.1.5 | Long |
:sub | Subject: https://tools.ietf.org/html/rfc7519#section-4.1.2 | String |
here is a sample token:
{:jti "r3e03ac6e-8d09-4d5e-8598-30e51a26cd2a"
:exp 1499419023
:iat 1498814223
:nbf 1498813923
:sub "f0010924-e1bc-4b03-b600-89c6cf52757c"
:email "foo@bar.com"
"http://example.com/claim/user/name" "john doe"}
A simple script is available to generate keys for signing the tokens:
> ./resources/cert/gen_cert.sh
some dummy ones are already available for easy testing.
- use
ring-jwt-middleware.core-test/make-jwt
to generate a sample token from a map
Copyright © 2015-2021 Cisco Systems Eclipse Public License v1.0