Skip to content

threatgrid/ring-jwt-middleware

Repository files navigation

https://img.shields.io/clojars/v/threatgrid/ring-jwt-middleware.svg

https://travis-ci.org/threatgrid/ring-jwt-middleware.png?branch=master

ring-jwt-middleware

A simple middleware to authenticate users using JWT (JSON Web Tokens) currently, only RS256 is supported.

Features

  • 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

Usage

Quickly

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)

Authentication

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 JWT
  • identity => the object representing the user identity constructed using JWT claims
  • jwt-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)))

Options

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.

JWT Format

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:

ClaimDescriptionFormat
:expExpiration time: https://tools.ietf.org/html/rfc7519#section-4.1.4Long
:iatIssued At: https://tools.ietf.org/html/rfc7519#section-4.1.6Long
:jtiJWT ID: https://tools.ietf.org/html/rfc7519#section-4.1.7String
:nbfNot Before: https://tools.ietf.org/html/rfc7519#section-4.1.5Long
:subSubject: https://tools.ietf.org/html/rfc7519#section-4.1.2String

here is a sample token:

{:jti "r3e03ac6e-8d09-4d5e-8598-30e51a26cd2a"
 :exp 1499419023
 :iat 1498814223
 :nbf 1498813923
 :sub "f0010924-e1bc-4b03-b600-89c6cf52757c"

 :email "[email protected]"
 "http://example.com/claim/user/name" "john doe"}

Generating Certs and a Token

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

License

Copyright © 2015-2021 Cisco Systems Eclipse Public License v1.0