Skip to content

Commit f0aafb9

Browse files
[SDK-5703] [GH-283] Add support for exclusion URLs in JWT middleware (#319)
1 parent 410ba07 commit f0aafb9

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

middleware.go

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type JWTMiddleware struct {
1717
tokenExtractor TokenExtractor
1818
credentialsOptional bool
1919
validateOnOptions bool
20+
exclusionUrlHandler ExclusionUrlHandler
2021
}
2122

2223
// ValidateToken takes in a string JWT and makes sure it is valid and
@@ -26,6 +27,10 @@ type JWTMiddleware struct {
2627
// In the default implementation we can add safe defaults for those.
2728
type ValidateToken func(context.Context, string) (interface{}, error)
2829

30+
// ExclusionUrlHandler is a function that takes in a http.Request and returns
31+
// true if the request should be excluded from JWT validation.
32+
type ExclusionUrlHandler func(r *http.Request) bool
33+
2934
// New constructs a new JWTMiddleware instance with the supplied options.
3035
// It requires a ValidateToken function to be passed in, so it can
3136
// properly validate tokens.
@@ -49,6 +54,11 @@ func New(validateToken ValidateToken, opts ...Option) *JWTMiddleware {
4954
// is passed a http.Handler which will be called if the JWT passes validation.
5055
func (m *JWTMiddleware) CheckJWT(next http.Handler) http.Handler {
5156
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57+
// If there's an exclusion handler and the URL matches, skip JWT validation
58+
if m.exclusionUrlHandler != nil && m.exclusionUrlHandler(r) {
59+
next.ServeHTTP(w, r)
60+
return
61+
}
5262
// If we don't validate on OPTIONS and this is OPTIONS
5363
// then continue onto next without validating.
5464
if !m.validateOnOptions && r.Method == http.MethodOptions {

middleware_test.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func Test_CheckJWT(t *testing.T) {
4646
wantToken interface{}
4747
wantStatusCode int
4848
wantBody string
49+
path string
4950
}{
5051
{
5152
name: "it can successfully validate a token",
@@ -133,6 +134,50 @@ func Test_CheckJWT(t *testing.T) {
133134
wantStatusCode: http.StatusBadRequest,
134135
wantBody: `{"message":"JWT is missing."}`,
135136
},
137+
{
138+
name: "JWT not required for /public",
139+
options: []Option{
140+
WithExclusionUrls([]string{"/public", "/health", "/special"}),
141+
},
142+
method: http.MethodGet,
143+
path: "/public",
144+
token: "",
145+
wantStatusCode: http.StatusOK,
146+
wantBody: `{"message":"Authenticated."}`,
147+
},
148+
{
149+
name: "JWT not required for /health",
150+
options: []Option{
151+
WithExclusionUrls([]string{"/public", "/health", "/special"}),
152+
},
153+
method: http.MethodGet,
154+
path: "/health",
155+
token: "",
156+
wantStatusCode: http.StatusOK,
157+
wantBody: `{"message":"Authenticated."}`,
158+
},
159+
{
160+
name: "JWT not required for /special",
161+
options: []Option{
162+
WithExclusionUrls([]string{"/public", "/health", "/special"}),
163+
},
164+
method: http.MethodGet,
165+
path: "/special",
166+
token: "",
167+
wantStatusCode: http.StatusOK,
168+
wantBody: `{"message":"Authenticated."}`,
169+
},
170+
{
171+
name: "JWT required for /secure (not in exclusion list)",
172+
options: []Option{
173+
WithExclusionUrls([]string{"/public", "/health", "/special"}),
174+
},
175+
method: http.MethodGet,
176+
path: "/secure",
177+
token: "",
178+
wantStatusCode: http.StatusBadRequest,
179+
wantBody: `{"message":"JWT is missing."}`,
180+
},
136181
}
137182

138183
for _, testCase := range testCases {
@@ -154,7 +199,8 @@ func Test_CheckJWT(t *testing.T) {
154199
testServer := httptest.NewServer(middleware.CheckJWT(handler))
155200
defer testServer.Close()
156201

157-
request, err := http.NewRequest(testCase.method, testServer.URL, nil)
202+
url := testServer.URL + testCase.path
203+
request, err := http.NewRequest(testCase.method, url, nil)
158204
require.NoError(t, err)
159205

160206
if testCase.token != "" {

option.go

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package jwtmiddleware
22

3+
import (
4+
"net/http"
5+
)
6+
37
// Option is how options for the JWTMiddleware are set up.
48
type Option func(*JWTMiddleware)
59

@@ -44,3 +48,21 @@ func WithTokenExtractor(e TokenExtractor) Option {
4448
m.tokenExtractor = e
4549
}
4650
}
51+
52+
// WithExclusionUrls allows configuring the exclusion URL handler with multiple URLs
53+
// that should be excluded from JWT validation.
54+
func WithExclusionUrls(exclusions []string) Option {
55+
return func(m *JWTMiddleware) {
56+
m.exclusionUrlHandler = func(r *http.Request) bool {
57+
requestFullURL := r.URL.String()
58+
requestPath := r.URL.Path
59+
60+
for _, exclusion := range exclusions {
61+
if requestFullURL == exclusion || requestPath == exclusion {
62+
return true
63+
}
64+
}
65+
return false
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)