You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Yann Esposito 2aa346b96f
Merge pull request #14 from yogsototh/log-levels
2 years ago
doc initial commit 3 years ago
resources/cert initial commit 3 years ago
src/ring_jwt_middleware better log levels, many should be WARN and not ERROR 2 years ago
test/ring_jwt_middleware generic jwt to identity support with prefix 2 years ago
.gitignore initial commit 3 years ago
.travis.yml added travis.yml 3 years ago
LICENSE initial commit 3 years ago
README.org provide a way to support long lived JWT 2 years ago
project.clj lein ancient upgrade 2 years ago

README.org

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 a user provided fn
  • custom revokation check through a user provided fn

Usage

Middleware & options

Use wrap-jwt-auth-fn to create an instance of the middleware, wrap your routes with it:


(let [wrap-jwt
      (wrap-jwt-auth-fn {:pubkey-path jwt-cert-path
                         :is-revoked-fn revoked?
                         :jwt-max-lifetime-in-sec jwt-lifetime
                         :jwt-check-fn check-jwt-fields
                         :no-jwt-handler authorize-no-jwt-header-strategy})]
  (api (api-data url-prefix)
        (middleware [wrap-jwt]
          (routes service url-prefix))))
Option Description Default
:pubkey-path the path to your public key nil
:jwt-max-lifetime-in-sec set a max lifetime for JWTs to expire 86400
:is-revoked-fn a fn to checks if a given JWT should be revoked, should return bool nil
:jwt-check-fn a fn to custom check the JWT, should return a vec of error strings or nil nil
:no-jwt-handler a middleware to pass when no JWT header is found (handler -> (request -> response)) forbid-no-jwt-header-strategy
:post-jwt-format-fn a fn that given a JWT generate an identity information jwt->user-id (the "sub" claim)
:long-lived-jwt? a fn that given a JWT return true if we should check the max-lifetime false otherwise jwt->user-id (the "sub" claim)

If the request contains a valid JWT auth header, the JWT is merged with the ring request under a :jwt key as well with a :identiy key. Otherwise the request is passed to :no-jwt-handler.

By default if no JWT auth header is found the request is terminated with =unauthorized= HTTP response. You can use authorize-no-jwt-header-strategy for the :no-jwt-handler key if you want to manage how to deal with that case in you own handler. You could also provide your own middleware function for this case.

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 supportes JWTs using claims registered in the IANA "JSON Web Token Claims", which means you need to generate JWTs 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"}

Compojure-api support

You can check the JWT from the req with schemas and destructure it like the rest of the HTTP query, use :jwt-params.


(POST "/test" []
      :return {:foo s/Str
               :user_id s/Str}
               :body-params  [{lorem :- s/Str ""}]
               :summary "Does nothing"
               :jwt-params [foo :- s/Str
                            user_id :- s/Str
                            exp :- s/Num
                            {boolean_field :- s/Bool "false"}]
  {:status 200
   :body {:foo foo
          :user_id user_id}})

You could also directly use :identity to match the result of the =post-jwt-format-fn= applied to the decoded JWT.

Generating Certs and a Token

A simple script is available to generate certs 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-2018 Cisco Systems Eclipse Public License v1.0