Skip to content

Commit 387a396

Browse files
authored
Enable use of the request to build user's identity (#33)
* add post-jwt-format-with-request-fn function * add precedence test
1 parent d590598 commit 387a396

File tree

4 files changed

+57
-24
lines changed

4 files changed

+57
-24
lines changed

README.org

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ You can inject your own authorization rules, via:
103103
Notice you could add the following keys in the configuration passed to ~mk-wrap-authentication~, ~mk-wrap-authorization~ and ~wrap-jwt-auth-fn:~
104104

105105
#+begin_src clojure
106-
(s/defschema Config
107-
"Initialized internal Configuration"
106+
(s/defschema Config*
108107
(st/merge
109108
{:allow-unauthenticated-access?
110109
(describe s/Bool
@@ -118,9 +117,6 @@ Notice you could add the following keys in the configuration passed to ~mk-wrap-
118117
:jwt-max-lifetime-in-sec
119118
(describe s/Num
120119
"Maximal number of second a JWT does not expires")
121-
:post-jwt-format-fn
122-
(describe (s/=> s/Any JWTClaims)
123-
"A function taking the JWT claims and building an Identity object suitable for your needs")
124120
:error-handler
125121
(describe (s/=> s/Any)
126122
"A function that given a JWTError returns a ring response.")
@@ -129,18 +125,33 @@ Notice you could add the following keys in the configuration passed to ~mk-wrap-
129125
(describe s/Num
130126
"When the JWT does not contain any nbf claim, the number of seconds to remove from iat claim. Default 60.")}
131127
(st/optional-keys
132-
{:pubkey-fn (describe (s/=> s/Any s/Str)
128+
{:post-jwt-format-fn
129+
(describe (s/=> s/Any JWTClaims)
130+
"A function taking the JWT claims and building an Identity object suitable for your needs")
131+
:post-jwt-format-with-request-fn
132+
(describe (s/=> s/Any JWTClaims)
133+
"A function taking the JWT claims and the request, and building an Identity object suitable for your needs")
134+
:pubkey-fn (describe (s/=> s/Any s/Str)
133135
"A function returning a public key (takes precedence over pubkey-path)")
134136
:pubkey-fn-arg-fn (describe (s/=> s/Any s/Any)
135137
"A function that will be applied to the argument (the raw JWT) of `pubkey-fn`")
136138
:post-jwt-format-fn-arg-fn (describe (s/=> s/Any s/Any)
137-
"A function that will be applied to the argument (the raw JWT) of `post-jwt-format-fn`")
139+
"A function that will be applied to the argument (the raw JWT) of `post-jwt-format-fn` or `post-jwt-format-with-request-fn`")
138140
:pubkey-path (describe s/Str
139141
"The path to find the public key that will be used to check the JWT signature")
140142
:jwt-check-fn
141143
(describe (s/=> s/Bool JWT JWTClaims)
142144
(str "A function that take a JWT, claims and return a sequence of string containing errors."
143145
"The check is considered successful if this function returns nil, or a sequence containing only nil values."))})))
146+
147+
(s/defschema Config
148+
"Initialized internal Configuration"
149+
(s/constrained
150+
Config*
151+
(fn [{:keys [post-jwt-format-fn post-jwt-format-with-request-fn]}]
152+
(or post-jwt-format-fn
153+
post-jwt-format-with-request-fn))
154+
"One of `post-jwt-format-fn` or `post-jwt-format-with-request-fn` is required. `post-jwt-format-with-request-fn` has precedence."))
144155
#+end_src
145156

146157
By default if no JWT authorization header is found the request is terminated with

src/ring_jwt_middleware/core.clj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
pubkey-fn
166166
is-revoked-fn
167167
post-jwt-format-fn
168+
post-jwt-format-with-request-fn
168169
post-jwt-format-fn-arg-fn
169170
pubkey-fn-arg-fn]
170171
:as config} (->config user-config)
@@ -188,7 +189,9 @@
188189
{:level :error
189190
:exception e
190191
:jwt jwt})))]
191-
(->pure {:identity (post-jwt-format-fn (post-jwt-format-fn-arg-fn jwt))
192+
(->pure {:identity (if post-jwt-format-with-request-fn
193+
(post-jwt-format-with-request-fn (post-jwt-format-fn-arg-fn jwt) request)
194+
(post-jwt-format-fn (post-jwt-format-fn-arg-fn jwt)))
192195
:jwt (:claims jwt)}))]
193196
(handler (into request (<-result authentication-result))))))))
194197

src/ring_jwt_middleware/schemas.clj

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@
4242
(with-meta s {:description description})
4343
s))
4444

45-
(s/defschema Config
46-
"Initialized internal Configuration"
45+
(s/defschema Config*
4746
(st/merge
4847
{:allow-unauthenticated-access?
4948
(describe s/Bool
@@ -57,31 +56,41 @@
5756
:jwt-max-lifetime-in-sec
5857
(describe s/Num
5958
"Maximal number of second a JWT does not expires")
60-
:post-jwt-format-fn
61-
(describe (s/=> s/Any JWTClaims)
62-
"A function taking the JWT claims and building an Identity object suitable for your needs")
6359
:error-handler
6460
(describe (s/=> s/Any)
6561
"A function that given a JWTError returns a ring response.")
66-
6762
:default-allowed-clock-skew-in-seconds
6863
(describe s/Num
6964
"When the JWT does not contain any nbf claim, the number of seconds to remove from iat claim. Default 60.")}
7065
(st/optional-keys
71-
{:pubkey-fn (describe (s/=> s/Any s/Str)
66+
{:post-jwt-format-fn
67+
(describe (s/=> s/Any JWTClaims)
68+
"A function taking the JWT claims and building an Identity object suitable for your needs")
69+
:post-jwt-format-with-request-fn
70+
(describe (s/=> s/Any JWTClaims)
71+
"A function taking the JWT claims and the request, and building an Identity object suitable for your needs")
72+
:pubkey-fn (describe (s/=> s/Any s/Str)
7273
"A function returning a public key (takes precedence over pubkey-path)")
7374
:pubkey-fn-arg-fn (describe (s/=> s/Any s/Any)
7475
"A function that will be applied to the argument (the raw JWT) of `pubkey-fn`")
7576
:post-jwt-format-fn-arg-fn (describe (s/=> s/Any s/Any)
76-
"A function that will be applied to the argument (the raw JWT) of `post-jwt-format-fn`")
77+
"A function that will be applied to the argument (the raw JWT) of `post-jwt-format-fn` or `post-jwt-format-with-request-fn`")
7778
:pubkey-path (describe s/Str
7879
"The path to find the public key that will be used to check the JWT signature")
7980
:jwt-check-fn
8081
(describe (s/=> s/Bool JWT JWTClaims)
8182
(str "A function that take a JWT, claims and return a sequence of string containing errors."
8283
"The check is considered successful if this function returns nil, or a sequence containing only nil values."))})))
8384

85+
(s/defschema Config
86+
"Initialized internal Configuration"
87+
(s/constrained
88+
Config*
89+
(fn [{:keys [post-jwt-format-fn post-jwt-format-with-request-fn]}]
90+
(or post-jwt-format-fn
91+
post-jwt-format-with-request-fn))
92+
"One of `post-jwt-format-fn` or `post-jwt-format-with-request-fn` is required. `post-jwt-format-with-request-fn` has precedence."))
8493

8594
(s/defschema UserConfig
8695
"Middleware Configuration"
87-
(st/optional-keys Config))
96+
(st/optional-keys Config*))

test/ring_jwt_middleware/core_test.clj

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
[& args]
4747
(constantly (to-epoch (apply jt/local-date-time args))))
4848

49-
5049
(jt/available-zone-ids)
5150

5251
(def jwt-token-1
@@ -150,7 +149,7 @@
150149

151150
(deftest validate-errors-test
152151
(let [cfg (config/->config {:current-epoch fixed-current-epoch
153-
:pubkey-path "resources/cert/jwt-key-1.pub"})]
152+
:pubkey-path "resources/cert/jwt-key-1.pub"})]
154153

155154
(is (result/success? (sut/validate-jwt cfg "jwt" decoded-jwt-1-claims)))
156155
(is (= {:jwt-error {:jwt {}
@@ -389,7 +388,6 @@
389388
(is (= 200
390389
(:status (ring-fn req))))))
391390

392-
393391
(testing "multiple keys support"
394392
(let [pubkey-fn (fn [claims]
395393
(case (:iss claims)
@@ -461,7 +459,6 @@
461459
{:error :no_jwt, :error_description "No JWT found in HTTP headers"}}}
462460
(ring-fn req-auth-header-not-jwt)))))
463461

464-
465462
(testing "revocation test"
466463
(let [revoke-handler (handler-with-mid-cfg {:is-revoked-fn (constantly true)})
467464
no-revoke-handler (handler-with-mid-cfg {:is-revoked-fn (constantly false)})]
@@ -475,12 +472,11 @@
475472
(is (= {:error :internal-error
476473
:error_description "Internal Error"}
477474
(select-keys (:body (revoke-handler req))
478-
[:error :error_description]))
475+
[:error :error_description]))
479476
"is-revoked-fn can provide specific errors")
480477
(is (= 200 (:status (no-revoke-handler req))))
481478
482-
(get-in (no-revoke-handler req) [:body :identity]))))
483-
)
479+
(get-in (no-revoke-handler req) [:body :identity])))))
484480

485481
(testing "post jwt transformation test"
486482
(let [post-transform (fn [m] {:user {:id (:sub m)}
@@ -491,6 +487,20 @@
491487
:org {:id "bar"}}
492488
(get-in (ring-fn req) [:body :identity])))))
493489

490+
(testing "post-jwt-format-with-request-fn takes precedence over post-jwt-format-fn"
491+
(let [post-transform (fn [m] {:user {:id (:sub m)}
492+
:org {:id (:foo m)}})
493+
post-transform-with-request (fn [m _req] {:user {:id (:sub m)}
494+
:org {:id (:foo m)}
495+
:headers-available? true})
496+
ring-fn (handler-with-mid-cfg {:post-jwt-format-fn post-transform
497+
:post-jwt-format-with-request-fn post-transform-with-request})]
498+
(is (= 200 (:status (ring-fn req))))
499+
(is (= {:user {:id "[email protected]"}
500+
:org {:id "bar"}
501+
:headers-available? true}
502+
(get-in (ring-fn req) [:body :identity])))))
503+
494504
(testing "post jwt transformation test using `post-jwt-format-fn-arg-fn`"
495505
(let [post-transform (fn [m] {:user {:id (-> m :claims :sub)}
496506
:org {:id (-> m :claims :foo)}

0 commit comments

Comments
 (0)