Skip to content

Commit e04af6c

Browse files
committed
feat: support multiple issuer:audience combinations by introducing an option for the expectedClaims. WithExpectedClaims can be called with multiple jwt.Expected parameters to allow different Issuer:Audience combinations to validate tokens
feat: support multiple issuers in a provider using WithAdditionalIssuers option Every effort has been made to ensure backwards compatibility. Some error messages will be different due to the wrapping of errors when multiple jwt.Expected are set. When validating the jwt, if an error is encountered, instead of returning immediately, the current error is wrapped. This is good and bad. Good because all verification failure causes are captured in a single wrapped error; Bad because all verification failure causes are captured in a single monolithic wrapped error. Unwrapping the error can be tedious if many jwt.Expected are included. There is likely a better way but this suits my purposes. A few more test cases will likely be needed in order to achieve true confidence in this change
1 parent b78a102 commit e04af6c

File tree

29 files changed

+692
-119
lines changed

29 files changed

+692
-119
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import (
4343
"log"
4444
"net/http"
4545

46-
"github.com/auth0/go-jwt-middleware/v2"
4746
"github.com/auth0/go-jwt-middleware/v2/validator"
4847
jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
4948
)

examples/echo-example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ To try this out:
1010
* Run `go run .` to start the app.
1111
* Use [jwt.io](https://jwt.io/) to generate a JWT signed with the HS256 algorithm and `secret`.
1212
* Call `http://localhost:3000` with the JWT to get a response back.
13+
* see `main.go` for example tokens

examples/echo-example/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
replace github.com/auth0/go-jwt-middleware/v2 => ./../../
1313

1414
require (
15+
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
1516
github.com/labstack/gommon v0.4.2 // indirect
1617
github.com/mattn/go-colorable v0.1.13 // indirect
1718
github.com/mattn/go-isatty v0.0.20 // indirect

examples/echo-example/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111

1212
// Try it out with:
1313
//
14-
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70
14+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.DSY4NlpZZ2mOqaKuXvJkOrgZA3nD5HuGaf1wB9-0OVw
1515
//
16-
// which is signed with 'secret' and has the data:
16+
// which is signed with 'abcdefghijklmnopqrstuvwxyz012345' and has the data:
1717
//
1818
// {
1919
// "iss": "go-jwt-middleware-example",
@@ -26,9 +26,9 @@ import (
2626
//
2727
// You can also try out the custom validation with:
2828
//
29-
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyIsInNob3VsZFJlamVjdCI6dHJ1ZX0.Jf13PY_Oyu2x3Gx1JQ0jXRiWaCOb5T2RbKOrTPBNHJA
29+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyIsInNob3VsZFJlamVjdCI6dHJ1ZX0.qjjJBgKNomlbEQrCobpEU9ASgvSpLQhQBryRkp6-RQc
3030
//
31-
// which is signed with 'secret' and has the data:
31+
// which is signed with 'abcdefghijklmnopqrstuvwxyz012345' and has the data:
3232
//
3333
// {
3434
// "iss": "go-jwt-middleware-example",

examples/echo-example/middleware.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
var (
1515
// The signing key for the token.
16-
signingKey = []byte("secret")
16+
signingKey = []byte("abcdefghijklmnopqrstuvwxyz012345")
1717

1818
// The issuer of our token.
1919
issuer = "go-jwt-middleware-example"

examples/gin-example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ To try this out:
1010
* Run `go run .` to start the app.
1111
* Use [jwt.io](https://jwt.io/) to generate a JWT signed with the HS256 algorithm and `secret`.
1212
* Call `http://localhost:3000` with the JWT to get a response back.
13+
* see `main.go` for example tokens

examples/gin-example/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ go 1.23.0
44

55
require (
66
github.com/auth0/go-jwt-middleware/v2 v2.2.2
7-
github.com/gin-gonic/gin v1.9.1
7+
github.com/gin-gonic/gin v1.10.0
8+
github.com/go-jose/go-jose/v4 v4.0.4
89
)
910

1011
replace github.com/auth0/go-jwt-middleware/v2 => ./../../

examples/gin-example/main.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111

1212
// Try it out with:
1313
//
14-
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.XFhrzWzntyINkgoRt2mb8dES84dJcuOoORdzKfwUX70
14+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.DSY4NlpZZ2mOqaKuXvJkOrgZA3nD5HuGaf1wB9-0OVw
1515
//
16-
// which is signed with 'secret' and has the data:
16+
// which is signed with 'abcdefghijklmnopqrstuvwxyz012345' and has the data:
1717
//
1818
// {
1919
// "iss": "go-jwt-middleware-example",
@@ -26,9 +26,9 @@ import (
2626
//
2727
// You can also try out the custom validation with:
2828
//
29-
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyIsInNob3VsZFJlamVjdCI6dHJ1ZX0.Jf13PY_Oyu2x3Gx1JQ0jXRiWaCOb5T2RbKOrTPBNHJA
29+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyIsInNob3VsZFJlamVjdCI6dHJ1ZX0.qjjJBgKNomlbEQrCobpEU9ASgvSpLQhQBryRkp6-RQc
3030
//
31-
// which is signed with 'secret' and has the data:
31+
// which is signed with 'abcdefghijklmnopqrstuvwxyz012345' and has the data:
3232
//
3333
// {
3434
// "iss": "go-jwt-middleware-example",
@@ -39,9 +39,29 @@ import (
3939
// "username": "user123",
4040
// "shouldReject": true
4141
// }
42+
//
43+
// You can also try out the /multiple endpoint. This endpoint accepts tokens signed by multiple issuers. Try the
44+
// token below which has a different issuer:
45+
//
46+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnby1qd3QtbWlkZGxld2FyZS1tdWx0aXBsZS1leGFtcGxlIiwiYXVkIjoiYXVkaWVuY2UtbXVsdGlwbGUtZXhhbXBsZSIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoidXNlcjEyMyJ9.d0BmhdqVZ9IdqQNId3XI2kbTegwW5NYe9P4xQCOjQ1Y
47+
//
48+
// which is signed with 'abcdefghijklmnopqrstuvwxyz012345' and has the data:
49+
//
50+
// {
51+
// "iss": "go-jwt-middleware-multiple-example",
52+
// "aud": "audience-multiple-example",
53+
// "sub": "1234567890",
54+
// "name": "John Doe",
55+
// "iat": 1516239022,
56+
// "username": "user123"
57+
// }
58+
//
59+
// You can also try the previous tokens with the /multiple endpoint. The first token will be valid the second will fail because
60+
// the custom validator rejects it (shouldReject: true)
4261

4362
func main() {
4463
router := gin.Default()
64+
4565
router.GET("/", checkJWT(), func(ctx *gin.Context) {
4666
claims, ok := ctx.Request.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
4767
if !ok {
@@ -52,7 +72,37 @@ func main() {
5272
return
5373
}
5474

55-
customClaims, ok := claims.CustomClaims.(*CustomClaimsExample)
75+
localCustomClaims, ok := claims.CustomClaims.(*CustomClaimsExample)
76+
if !ok {
77+
ctx.AbortWithStatusJSON(
78+
http.StatusInternalServerError,
79+
map[string]string{"message": "Failed to cast custom JWT claims to specific type."},
80+
)
81+
return
82+
}
83+
84+
if len(localCustomClaims.Username) == 0 {
85+
ctx.AbortWithStatusJSON(
86+
http.StatusBadRequest,
87+
map[string]string{"message": "Username in JWT claims was empty."},
88+
)
89+
return
90+
}
91+
92+
ctx.JSON(http.StatusOK, claims)
93+
})
94+
95+
router.GET("/multiple", checkJWTMultiple(), func(ctx *gin.Context) {
96+
claims, ok := ctx.Request.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims)
97+
if !ok {
98+
ctx.AbortWithStatusJSON(
99+
http.StatusInternalServerError,
100+
map[string]string{"message": "Failed to get validated JWT claims."},
101+
)
102+
return
103+
}
104+
105+
localCustomClaims, ok := claims.CustomClaims.(*CustomClaimsExample)
56106
if !ok {
57107
ctx.AbortWithStatusJSON(
58108
http.StatusInternalServerError,
@@ -61,7 +111,7 @@ func main() {
61111
return
62112
}
63113

64-
if len(customClaims.Username) == 0 {
114+
if len(localCustomClaims.Username) == 0 {
65115
ctx.AbortWithStatusJSON(
66116
http.StatusBadRequest,
67117
map[string]string{"message": "Username in JWT claims was empty."},

examples/gin-example/middleware.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"github.com/go-jose/go-jose/v4/jwt"
56
"log"
67
"net/http"
78
"time"
@@ -13,13 +14,15 @@ import (
1314

1415
var (
1516
// The signing key for the token.
16-
signingKey = []byte("secret")
17+
signingKey = []byte("abcdefghijklmnopqrstuvwxyz012345")
1718

1819
// The issuer of our token.
19-
issuer = "go-jwt-middleware-example"
20+
issuer = "go-jwt-middleware-example"
21+
issuerTwo = "go-jwt-middleware-multiple-example"
2022

2123
// The audience of our token.
22-
audience = []string{"audience-example"}
24+
audience = []string{"audience-example"}
25+
audienceTwo = []string{"audience-multiple-example"}
2326

2427
// Our token must be signed using this data.
2528
keyFunc = func(ctx context.Context) (interface{}, error) {
@@ -76,3 +79,50 @@ func checkJWT() gin.HandlerFunc {
7679
}
7780
}
7881
}
82+
83+
func checkJWTMultiple() gin.HandlerFunc {
84+
// Set up the validator.
85+
jwtValidator, err := validator.NewValidator(
86+
keyFunc,
87+
validator.HS256,
88+
validator.WithCustomClaims(customClaims),
89+
validator.WithAllowedClockSkew(30*time.Second),
90+
validator.WithExpectedClaims(jwt.Expected{
91+
Issuer: issuer,
92+
AnyAudience: audience,
93+
}, jwt.Expected{
94+
Issuer: issuerTwo,
95+
AnyAudience: audienceTwo,
96+
}),
97+
)
98+
if err != nil {
99+
log.Fatalf("failed to set up the validator: %v", err)
100+
}
101+
102+
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
103+
log.Printf("Encountered error while validating JWT: %v", err)
104+
}
105+
106+
middleware := jwtmiddleware.New(
107+
jwtValidator.ValidateToken,
108+
jwtmiddleware.WithErrorHandler(errorHandler),
109+
)
110+
111+
return func(ctx *gin.Context) {
112+
encounteredError := true
113+
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
114+
encounteredError = false
115+
ctx.Request = r
116+
ctx.Next()
117+
}
118+
119+
middleware.CheckJWT(handler).ServeHTTP(ctx.Writer, ctx.Request)
120+
121+
if encounteredError {
122+
ctx.AbortWithStatusJSON(
123+
http.StatusUnauthorized,
124+
map[string]string{"message": "JWT is invalid."},
125+
)
126+
}
127+
}
128+
}

examples/http-example/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ To try this out:
1010
* Run `go run main.go` to start the app.
1111
* Use [jwt.io](https://jwt.io/) to generate a JWT signed with the HS256 algorithm and `secret`.
1212
* Call `http://localhost:3000` with the JWT to get a response back.
13+
* see `main.go` for example tokens

0 commit comments

Comments
 (0)