From 6635f4beea4216503918c9e3a8fe19f615963833 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Tue, 19 Jan 2016 17:43:58 -0500 Subject: [PATCH 1/9] Check exp claim if its provided --- LICENSE | 0 README.md | 20 +++++++++++++++++++- jwtauth.go | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 2769561..b1e7c84 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ -# jwtauth +Copyright (c) 2015-2016 Peter Kieltyka (https://twitter.com/peterk) +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/jwtauth.go b/jwtauth.go index d928140..ce384e6 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" "strings" + "time" "github.com/dgrijalva/jwt-go" "github.com/pressly/chi" @@ -88,6 +89,15 @@ func (ja *JwtAuth) Handle(paramAliases ...string) func(chi.Handler) chi.Handler return } + // Check expiry via "exp" claim + if exp, ok := token.Claims["exp"].(int64); ok { + now := EpochNow() + if exp < now { + http.Error(w, errUnauthorized.Error(), 401) + return + } + } + ctx = context.WithValue(ctx, "jwt", token.Raw) ctx = context.WithValue(ctx, "jwt.token", token) @@ -123,3 +133,8 @@ func (ja *JwtAuth) Decode(tokenString string) (t *jwt.Token, err error) { } return jwt.Parse(tokenString, ja.keyFunc) } + +// Return the NumericDate time value used in conventional jwt claims +func EpochNow() int64 { + return time.Now().UTC().Unix() +} From 66c0c85e36856856f2f5fe168725ab730265a776 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Tue, 19 Jan 2016 17:44:41 -0500 Subject: [PATCH 2/9] Revert README --- LICENSE | 20 ++++++++++++++++++++ README.md | 21 +-------------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index e69de29..b1e7c84 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-2016 Peter Kieltyka (https://twitter.com/peterk) + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index b1e7c84..6d0d001 100644 --- a/README.md +++ b/README.md @@ -1,20 +1 @@ -Copyright (c) 2015-2016 Peter Kieltyka (https://twitter.com/peterk) - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# jwtauth From a872c75843ac43ea98363a32ad3da09e67a44184 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:00:36 -0500 Subject: [PATCH 3/9] Handler improvements, Claims type, expiry and tests --- jwtauth.go | 154 +++++++++++++++++++++++++++++++------ jwtauth_test.go | 201 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 332 insertions(+), 23 deletions(-) create mode 100644 jwtauth_test.go diff --git a/jwtauth.go b/jwtauth.go index ce384e6..9bb063b 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -12,7 +12,8 @@ import ( ) var ( - errUnauthorized = errors.New("unauthorized token") + ErrUnauthorized = errors.New("jwtauth: unauthorized token") + ErrExpired = errors.New("jwtauth: expired token") ) type JwtAuth struct { @@ -22,7 +23,8 @@ type JwtAuth struct { parser *jwt.Parser } -// verifyKey is only for RSA +// New creates a JwtAuth authenticator instance that provides middleware handlers +// and encoding/decoding functions for JWT signing. func New(alg string, signKey []byte, verifyKey []byte) *JwtAuth { return &JwtAuth{ signKey: signKey, @@ -31,7 +33,8 @@ func New(alg string, signKey []byte, verifyKey []byte) *JwtAuth { } } -// the same as New, except it supports custom parser settings introduced in ver. 2.4.0 of jwt-go +// NewWithParser is the same as New, except it supports custom parser settings +// introduced in ver. 2.4.0 of jwt-go func NewWithParser(alg string, parser *jwt.Parser, signKey []byte, verifyKey []byte) *JwtAuth { return &JwtAuth{ signKey: signKey, @@ -41,7 +44,25 @@ func NewWithParser(alg string, parser *jwt.Parser, signKey []byte, verifyKey []b } } -func (ja *JwtAuth) Handle(paramAliases ...string) func(chi.Handler) chi.Handler { +// Verifier middleware will verify a JWT passed by a client request. +// The Verifier will look for a JWT token from: +// 1. 'jwt' URI query parameter +// 2. 'Authorization: BEARER T' request header +// 3. Cookie 'jwt' value +// +// The verification processes finishes here and sets the token and +// a error in the request context and calls the next handler. +// +// Make sure to have your own handler following the Validator that +// will check the value of the "jwt" and "jwt.err" in the context +// and respond to the client accordingly. A generic Authenticator +// middleware is provided by this package, that will return a 401 +// message for all unverified tokens, see jwtauth.Authenticator. +func (ja *JwtAuth) Verifier(next chi.Handler) chi.Handler { + return ja.Verify("")(next) +} + +func (ja *JwtAuth) Verify(paramAliases ...string) func(chi.Handler) chi.Handler { return func(next chi.Handler) chi.Handler { hfn := func(ctx context.Context, w http.ResponseWriter, r *http.Request) { @@ -79,39 +100,45 @@ func (ja *JwtAuth) Handle(paramAliases ...string) func(chi.Handler) chi.Handler // Token is required, cya if tokenStr == "" { - err = errUnauthorized + err = ErrUnauthorized } // Verify the token token, err := ja.Decode(tokenStr) if err != nil || !token.Valid || token.Method != ja.signer { - http.Error(w, errUnauthorized.Error(), 401) + switch err.Error() { + case "token is expired": + err = ErrExpired + } + + ctx = ja.SetContext(ctx, token, err) + next.ServeHTTPC(ctx, w, r) return } // Check expiry via "exp" claim - if exp, ok := token.Claims["exp"].(int64); ok { - now := EpochNow() - if exp < now { - http.Error(w, errUnauthorized.Error(), 401) - return - } + if ja.IsExpired(token) { + err = ErrExpired + ctx = ja.SetContext(ctx, token, err) + next.ServeHTTPC(ctx, w, r) + return } - ctx = context.WithValue(ctx, "jwt", token.Raw) - ctx = context.WithValue(ctx, "jwt.token", token) - + // Valid! pass it down the context to an authenticator middleware + ctx = ja.SetContext(ctx, token, err) next.ServeHTTPC(ctx, w, r) } return chi.HandlerFunc(hfn) } } -func (ja *JwtAuth) Handler(next chi.Handler) chi.Handler { - return ja.Handle("")(next) +func (ja *JwtAuth) SetContext(ctx context.Context, t *jwt.Token, err error) context.Context { + ctx = context.WithValue(ctx, "jwt", t) + ctx = context.WithValue(ctx, "jwt.err", err) + return ctx } -func (ja *JwtAuth) Encode(claims map[string]interface{}) (t *jwt.Token, tokenString string, err error) { +func (ja *JwtAuth) Encode(claims Claims) (t *jwt.Token, tokenString string, err error) { t = jwt.New(ja.signer) t.Claims = claims tokenString, err = t.SignedString(ja.signKey) @@ -119,6 +146,13 @@ func (ja *JwtAuth) Encode(claims map[string]interface{}) (t *jwt.Token, tokenStr return } +func (ja *JwtAuth) Decode(tokenString string) (t *jwt.Token, err error) { + if ja.parser != nil { + return ja.parser.Parse(tokenString, ja.keyFunc) + } + return jwt.Parse(tokenString, ja.keyFunc) +} + func (ja *JwtAuth) keyFunc(t *jwt.Token) (interface{}, error) { if ja.verifyKey != nil && len(ja.verifyKey) > 0 { return ja.verifyKey, nil @@ -127,14 +161,88 @@ func (ja *JwtAuth) keyFunc(t *jwt.Token) (interface{}, error) { } } -func (ja *JwtAuth) Decode(tokenString string) (t *jwt.Token, err error) { - if ja.parser != nil { - return ja.parser.Parse(tokenString, ja.keyFunc) +func (ja *JwtAuth) IsExpired(t *jwt.Token) bool { + if expv, ok := t.Claims["exp"]; ok { + var exp int64 + now := EpochNow() + + switch v := expv.(type) { + case float64: + exp = int64(v) + case int64: + exp = v + default: + } + + if exp < now { + return true + } } - return jwt.Parse(tokenString, ja.keyFunc) + + return false } -// Return the NumericDate time value used in conventional jwt claims +// Authenticator is a default authentication middleware to enforce access following +// the Verifier middleware. The Authenticator sends a 401 Unauthorized response for +// all unverified tokens and passes the good ones through. It's just fine until you +// decide to write something similar and customize your client response. +func Authenticator(next chi.Handler) chi.Handler { + return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if jwtErr, ok := ctx.Value("jwt.err").(error); ok { + if jwtErr != nil { + http.Error(w, http.StatusText(401), 401) + return + } + } + + jwtToken, ok := ctx.Value("jwt").(*jwt.Token) + if !ok || jwtToken == nil || !jwtToken.Valid { + http.Error(w, http.StatusText(401), 401) + return + } + + // Token is authenticated, pass it through + next.ServeHTTPC(ctx, w, r) + }) +} + +// Claims is a convenience type to manage a JWT claims hash. +type Claims map[string]interface{} + +func (c Claims) Set(k string, v interface{}) Claims { + c[k] = v + return c +} + +func (c Claims) Get(k string) (interface{}, bool) { + v, ok := c[k] + return v, ok +} + +// Set issues at ("iat") to specified time in the claims +func (c Claims) SetIssuedAt(tm time.Time) Claims { + c["iat"] = tm.UTC().Unix() + return c +} + +// Set issues at ("iat") to present time in the claims +func (c Claims) SetIssuedNow() Claims { + c["iat"] = EpochNow() + return c +} + +// Set expiry ("exp") in the claims and return itself so it can be chained +func (c Claims) SetExpiryIn(tm time.Duration) Claims { + c["exp"] = ExpireIn(tm) + return c +} + +// Helper function that returns the NumericDate time value used by the spec func EpochNow() int64 { return time.Now().UTC().Unix() } + +// Helper function to return calculated time in the future for "exp" claim. +func ExpireIn(tm time.Duration) int64 { + return EpochNow() + int64(tm.Seconds()) +} diff --git a/jwtauth_test.go b/jwtauth_test.go new file mode 100644 index 0000000..3614c9f --- /dev/null +++ b/jwtauth_test.go @@ -0,0 +1,201 @@ +package jwtauth_test + +import ( + "io" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/goware/jwtauth" + "github.com/pressly/chi" + "golang.org/x/net/context" +) + +var ( + TokenAuth *jwtauth.JwtAuth + TokenSecret = []byte("secretpass") +) + +func init() { + TokenAuth = jwtauth.New("HS256", []byte("secretpass"), nil) +} + +// +// Tests +// + +func TestSimple(t *testing.T) { + r := chi.NewRouter() + + r.Use(TokenAuth.Verifier) + r.Use(jwtauth.Authenticator) + + r.Get("/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + + ts := httptest.NewServer(r) + defer ts.Close() + + // sending unauthorized requests + if resp := testRequest(t, ts, "GET", "/", nil, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + + h := http.Header{} + h.Set("Authorization", "BEARER "+newJwtToken([]byte("wrong"), map[string]interface{}{})) + if resp := testRequest(t, ts, "GET", "/", h, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + h.Set("Authorization", "BEARER asdf") + if resp := testRequest(t, ts, "GET", "/", h, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + + // sending authorized requests + if resp := testRequest(t, ts, "GET", "/", newAuthHeader(), nil); resp != "welcome" { + t.Fatalf(resp) + } +} + +func TestMore(t *testing.T) { + r := chi.NewRouter() + + // Protected routes + r.Group(func(r chi.Router) { + r.Use(TokenAuth.Verifier) + + authenticator := func(next chi.Handler) chi.Handler { + return chi.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if jwtErr, ok := ctx.Value("jwt.err").(error); ok { + switch jwtErr { + default: + log.Println("...we're expired... I think..:", jwtErr) + http.Error(w, http.StatusText(401), 401) + return + case jwtauth.ErrExpired: + http.Error(w, "expired", 401) + return + case jwtauth.ErrUnauthorized: + http.Error(w, http.StatusText(401), 401) + return + case nil: + // no error + } + } + + jwtToken, ok := ctx.Value("jwt").(*jwt.Token) + if !ok || jwtToken == nil || !jwtToken.Valid { + log.Println("jwt token..........?", jwtToken) + http.Error(w, http.StatusText(401), 401) + return + } + + // Token is authenticated, pass it through + next.ServeHTTPC(ctx, w, r) + }) + } + r.Use(authenticator) + + r.Get("/admin", func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + w.Write([]byte("protected")) + }) + }) + + // Public routes + r.Group(func(r chi.Router) { + r.Get("/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + }) + + ts := httptest.NewServer(r) + defer ts.Close() + + // sending unauthorized requests + if resp := testRequest(t, ts, "GET", "/admin", nil, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + + h := http.Header{} + h.Set("Authorization", "BEARER "+newJwtToken([]byte("wrong"), map[string]interface{}{})) + if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + h.Set("Authorization", "BEARER asdf") + if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "Unauthorized\n" { + t.Fatalf(resp) + } + + h = newAuthHeader((jwtauth.Claims{}).Set("exp", jwtauth.EpochNow()-1000)) + if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "expired\n" { + t.Fatalf(resp) + } + + // sending authorized requests + if resp := testRequest(t, ts, "GET", "/", nil, nil); resp != "welcome" { + t.Fatalf(resp) + } + + h = newAuthHeader((jwtauth.Claims{}).SetExpiryIn(5 * time.Minute)) + if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "protected" { + t.Fatalf(resp) + } +} + +// +// Test helper functions +// + +func testRequest(t *testing.T, ts *httptest.Server, method, path string, header http.Header, body io.Reader) string { + req, err := http.NewRequest(method, ts.URL+path, body) + if err != nil { + t.Fatal(err) + return "" + } + + if header != nil { + for k, v := range header { + req.Header.Set(k, v[0]) + } + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + return "" + } + + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + return "" + } + defer resp.Body.Close() + + return string(respBody) +} + +func newJwtToken(secret []byte, claims ...jwtauth.Claims) string { + token := jwt.New(jwt.GetSigningMethod("HS256")) + if len(claims) > 0 { + for k, v := range claims[0] { + token.Claims[k] = v + } + } + tokenStr, err := token.SignedString(secret) + if err != nil { + log.Fatal(err) + } + return tokenStr +} + +func newAuthHeader(claims ...jwtauth.Claims) http.Header { + h := http.Header{} + h.Set("Authorization", "BEARER "+newJwtToken(TokenSecret, claims...)) + return h +} From 079feacddbbae0c2c85bd23aa122ba51e5fcbe8b Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:07:01 -0500 Subject: [PATCH 4/9] Minor --- jwtauth.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jwtauth.go b/jwtauth.go index 9bb063b..aa8e889 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -164,8 +164,6 @@ func (ja *JwtAuth) keyFunc(t *jwt.Token) (interface{}, error) { func (ja *JwtAuth) IsExpired(t *jwt.Token) bool { if expv, ok := t.Claims["exp"]; ok { var exp int64 - now := EpochNow() - switch v := expv.(type) { case float64: exp = int64(v) @@ -174,7 +172,7 @@ func (ja *JwtAuth) IsExpired(t *jwt.Token) bool { default: } - if exp < now { + if exp < EpochNow() { return true } } From 4db4f7e78f22b416040c849d05b5bad6f901e7db Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:08:11 -0500 Subject: [PATCH 5/9] Typo --- jwtauth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jwtauth.go b/jwtauth.go index aa8e889..4e46864 100644 --- a/jwtauth.go +++ b/jwtauth.go @@ -217,13 +217,13 @@ func (c Claims) Get(k string) (interface{}, bool) { return v, ok } -// Set issues at ("iat") to specified time in the claims +// Set issued at ("iat") to specified time in the claims func (c Claims) SetIssuedAt(tm time.Time) Claims { c["iat"] = tm.UTC().Unix() return c } -// Set issues at ("iat") to present time in the claims +// Set issued at ("iat") to present time in the claims func (c Claims) SetIssuedNow() Claims { c["iat"] = EpochNow() return c From 5230a789a2333aeb1be33d8b4bebb7ad5bdd3bed Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:08:52 -0500 Subject: [PATCH 6/9] Remove debug statements --- jwtauth_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/jwtauth_test.go b/jwtauth_test.go index 3614c9f..640270c 100644 --- a/jwtauth_test.go +++ b/jwtauth_test.go @@ -74,7 +74,6 @@ func TestMore(t *testing.T) { if jwtErr, ok := ctx.Value("jwt.err").(error); ok { switch jwtErr { default: - log.Println("...we're expired... I think..:", jwtErr) http.Error(w, http.StatusText(401), 401) return case jwtauth.ErrExpired: @@ -90,7 +89,6 @@ func TestMore(t *testing.T) { jwtToken, ok := ctx.Value("jwt").(*jwt.Token) if !ok || jwtToken == nil || !jwtToken.Valid { - log.Println("jwt token..........?", jwtToken) http.Error(w, http.StatusText(401), 401) return } From 4c4d95448bff16ceabc30646b77db9aa71802868 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:09:44 -0500 Subject: [PATCH 7/9] Use test secret --- jwtauth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwtauth_test.go b/jwtauth_test.go index 640270c..329e4a1 100644 --- a/jwtauth_test.go +++ b/jwtauth_test.go @@ -21,7 +21,7 @@ var ( ) func init() { - TokenAuth = jwtauth.New("HS256", []byte("secretpass"), nil) + TokenAuth = jwtauth.New("HS256", TokenSecret, nil) } // From 2d088c1f3a0c8228d59bb1200bfe25e004fa8b73 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:12:19 -0500 Subject: [PATCH 8/9] Chi can chain middleware in one line, #fancy --- jwtauth_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jwtauth_test.go b/jwtauth_test.go index 329e4a1..e84b1d7 100644 --- a/jwtauth_test.go +++ b/jwtauth_test.go @@ -31,8 +31,7 @@ func init() { func TestSimple(t *testing.T) { r := chi.NewRouter() - r.Use(TokenAuth.Verifier) - r.Use(jwtauth.Authenticator) + r.Use(TokenAuth.Verifier, jwtauth.Authenticator) r.Get("/", func(ctx context.Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte("welcome")) From b2ccd8612d52d17562f71b268c312b4f2467929e Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 21 Jan 2016 14:19:49 -0500 Subject: [PATCH 9/9] Test status code in tests --- jwtauth_test.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/jwtauth_test.go b/jwtauth_test.go index e84b1d7..42b3b33 100644 --- a/jwtauth_test.go +++ b/jwtauth_test.go @@ -41,22 +41,22 @@ func TestSimple(t *testing.T) { defer ts.Close() // sending unauthorized requests - if resp := testRequest(t, ts, "GET", "/", nil, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/", nil, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } h := http.Header{} h.Set("Authorization", "BEARER "+newJwtToken([]byte("wrong"), map[string]interface{}{})) - if resp := testRequest(t, ts, "GET", "/", h, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/", h, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } h.Set("Authorization", "BEARER asdf") - if resp := testRequest(t, ts, "GET", "/", h, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/", h, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } // sending authorized requests - if resp := testRequest(t, ts, "GET", "/", newAuthHeader(), nil); resp != "welcome" { + if status, resp := testRequest(t, ts, "GET", "/", newAuthHeader(), nil); status != 200 && resp != "welcome" { t.Fatalf(resp) } } @@ -114,32 +114,32 @@ func TestMore(t *testing.T) { defer ts.Close() // sending unauthorized requests - if resp := testRequest(t, ts, "GET", "/admin", nil, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/admin", nil, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } h := http.Header{} h.Set("Authorization", "BEARER "+newJwtToken([]byte("wrong"), map[string]interface{}{})) - if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } h.Set("Authorization", "BEARER asdf") - if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "Unauthorized\n" { + if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 && resp != "Unauthorized\n" { t.Fatalf(resp) } h = newAuthHeader((jwtauth.Claims{}).Set("exp", jwtauth.EpochNow()-1000)) - if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "expired\n" { + if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 401 && resp != "expired\n" { t.Fatalf(resp) } // sending authorized requests - if resp := testRequest(t, ts, "GET", "/", nil, nil); resp != "welcome" { + if status, resp := testRequest(t, ts, "GET", "/", nil, nil); status != 200 && resp != "welcome" { t.Fatalf(resp) } h = newAuthHeader((jwtauth.Claims{}).SetExpiryIn(5 * time.Minute)) - if resp := testRequest(t, ts, "GET", "/admin", h, nil); resp != "protected" { + if status, resp := testRequest(t, ts, "GET", "/admin", h, nil); status != 200 && resp != "protected" { t.Fatalf(resp) } } @@ -148,11 +148,11 @@ func TestMore(t *testing.T) { // Test helper functions // -func testRequest(t *testing.T, ts *httptest.Server, method, path string, header http.Header, body io.Reader) string { +func testRequest(t *testing.T, ts *httptest.Server, method, path string, header http.Header, body io.Reader) (int, string) { req, err := http.NewRequest(method, ts.URL+path, body) if err != nil { t.Fatal(err) - return "" + return 0, "" } if header != nil { @@ -164,17 +164,17 @@ func testRequest(t *testing.T, ts *httptest.Server, method, path string, header resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) - return "" + return 0, "" } respBody, err := ioutil.ReadAll(resp.Body) if err != nil { t.Fatal(err) - return "" + return 0, "" } defer resp.Body.Close() - return string(respBody) + return resp.StatusCode, string(respBody) } func newJwtToken(secret []byte, claims ...jwtauth.Claims) string {