Skip to content

Commit

Permalink
add square/go-jose.v2 token validator (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
grounded042 authored May 25, 2021
1 parent 23c6193 commit 665e7da
Show file tree
Hide file tree
Showing 14 changed files with 716 additions and 98 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
args: -v --timeout=5m --exclude SA1029
args: -v --timeout=5m
skip-build-cache: true
skip-go-installation: true
skip-pkg-cache: true
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ test: ## Run tests.

.PHONY: lint
lint: ## Run golangci-lint.
golangci-lint run -v --timeout=5m --exclude SA1029
golangci-lint run -v --timeout=5m

.PHONY: help
help:
Expand Down
110 changes: 97 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,118 @@
# GO JWT Middleware

[![GoDoc Widget]][GoDoc]

**WARNING**
This `v2` branch is not production ready - use at your own risk.

TODO: update this README in the `v2` branch. We're waiting so as not to hold everything up in the testing branch. Also some of the default validation logic needs to be added here.
Golang middleware to check and validate [JWTs](jwt.io) in the request and add the valid token contents to the request context.

## Installation
```
go get github.com/auth0/go-jwt-middleware
```

## Usage
```golang
package main

import (
"context"
"encoding/json"
"fmt"
"net/http"

jwtmiddleware "github.com/auth0/go-jwt-middleware"
"github.com/auth0/go-jwt-middleware/validate/josev2"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)

## What is Auth0?
var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(jwtmiddleware.ContextKey{})
j, err := json.MarshalIndent(user, "", "\t")
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
}

Auth0 helps you to:
fmt.Fprintf(w, "This is an authenticated request")
fmt.Fprintf(w, "Claim content:\n")
fmt.Fprint(w, string(j))
})

* Add authentication with [multiple authentication sources](https://docs.auth0.com/identityproviders), either social like **Google, Facebook, Microsoft Account, LinkedIn, GitHub, Twitter, Box, Salesforce, amont others**, or enterprise identity systems like **Windows Azure AD, Google Apps, Active Directory, ADFS or any SAML Identity Provider**.
* Add authentication through more traditional **[username/password databases](https://docs.auth0.com/mysql-connection-tutorial)**.
* Add support for **[linking different user accounts](https://docs.auth0.com/link-accounts)** with the same user.
* Support for generating signed [Json Web Tokens](https://docs.auth0.com/jwt) to call your APIs and **flow the user identity** securely.
* Analytics of how, when and where users are logging in.
* Pull data from other sources and add it to the user profile, through [JavaScript rules](https://docs.auth0.com/rules).
func main() {
keyFunc := func(ctx context.Context) (interface{}, error) {
// our token must be signed using this data
return []byte("secret"), nil
}

## Create a free Auth0 Account
expectedClaimsFunc := func() jwt.Expected {
// By setting up expected claims we are saying a token must
// have the data we specify.
return jwt.Expected{
Issuer: "josev2-example",
}
}

1. Go to [Auth0](https://auth0.com) and click Sign Up.
2. Use Google, GitHub or Microsoft Account to login.
// setup the piece which will validate tokens
validator, err := josev2.New(
keyFunc,
jose.HS256,
josev2.WithExpectedClaims(expectedClaimsFunc),
)
if err != nil {
// we'll panic in order to fail fast
panic(err)
}

// setup the middleware
m := jwtmiddleware.New(validator.ValidateToken)

http.ListenAndServe("0.0.0.0:3000", m.CheckJWT(handler))
}
```

Running that code you can then curl it from another terminal:
```
$ curl -H Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpc3MiOiJqb3NldjItZXhhbXBsZSJ9.e0lGglk9-m-n-t07eA5f7qgXGM-nD4ekwJkYVKprIUM" localhost:3000
```
should give you the response
```
This is an authenticated requestClaim content:
{
"CustomClaims": null,
"Claims": {
"iss": "josev2-example",
"sub": "1234567890",
"iat": 1516239022
}
}
```
The JWT included in the Authorization header above is signed with `secret`.

To test it not working:
```
$ curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.yiDw9IDNCa1WXCoDfPR_g356vSsHBEerqh9IvnD49QE" localhost:3000
```
should give you a response like
```
...
< HTTP/1.1 401 Unauthorized
...
```

## Issue Reporting

If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.

## Author

[Auth0](auth0.com)
[Auth0](https://auth0.com/)

## License

This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.

[GoDoc]: https://pkg.go.dev/github.com/auth0/go-jwt-middleware
[GoDoc Widget]: https://pkg.go.dev/badge/github.com/auth0/go-jwt-middleware.svg
63 changes: 38 additions & 25 deletions examples/http-example/main.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,57 @@
package main

import (
"context"
"encoding/json"
"fmt"
"net/http"

jwtmiddleware "github.com/auth0/go-jwt-middleware"
"github.com/form3tech-oss/jwt-go"
"github.com/auth0/go-jwt-middleware/validate/josev2"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)

// TODO: replace this with default validate token func once it is merged in
func REPLACE_ValidateToken(token string) (interface{}, error) {
// Now parse the token
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte("My Secret"), nil
})

// Check if there was an error in parsing...
var handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(jwtmiddleware.ContextKey{})
j, err := json.MarshalIndent(user, "", "\t")
if err != nil {
return nil, err
}

// Check if the parsed token is valid...
if !parsedToken.Valid {
return nil, jwtmiddleware.ErrJWTInvalid
w.WriteHeader(http.StatusInternalServerError)
fmt.Println(err)
}

return parsedToken, nil
}

var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user")
fmt.Fprintf(w, "This is an authenticated request")
fmt.Fprintf(w, "Claim content:\n")
for k, v := range user.(*jwt.Token).Claims.(jwt.MapClaims) {
fmt.Fprintf(w, "%s :\t%#v\n", k, v)
}
fmt.Fprint(w, string(j))
})

func main() {
jwtMiddleware := jwtmiddleware.New(jwtmiddleware.WithValidateToken(REPLACE_ValidateToken))
keyFunc := func(ctx context.Context) (interface{}, error) {
// our token must be signed using this data
return []byte("secret"), nil
}

expectedClaimsFunc := func() jwt.Expected {
// By setting up expected claims we are saying a token must
// have the data we specify.
return jwt.Expected{
Issuer: "josev2-example",
}
}

// setup the piece which will validate tokens
validator, err := josev2.New(
keyFunc,
jose.HS256,
josev2.WithExpectedClaims(expectedClaimsFunc),
)
if err != nil {
// we'll panic in order to fail fast
panic(err)
}

// setup the middleware
m := jwtmiddleware.New(validator.ValidateToken)

http.ListenAndServe("0.0.0.0:3000", jwtMiddleware.CheckJWT(myHandler))
http.ListenAndServe("0.0.0.0:3000", m.CheckJWT(handler))
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/auth0/go-jwt-middleware
go 1.14

require (
github.com/form3tech-oss/jwt-go v3.2.2+incompatible
github.com/google/go-cmp v0.5.5
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
gopkg.in/square/go-jose.v2 v2.5.1
)
22 changes: 20 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
12 changes: 7 additions & 5 deletions jwtmiddleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type TokenExtractor func(r *http.Request) (string, error)
// error message describing why validation failed.
// Inside of ValidateToken is where things like key and alg checking can
// happen. In the default implementation we can add safe defaults for those.
type ValidateToken func(string) (interface{}, error)
type ValidateToken func(context.Context, string) (interface{}, error)

type JWTMiddleware struct {
validateToken ValidateToken
Expand Down Expand Up @@ -120,10 +120,12 @@ func WithValidateOnOptions(value bool) Option {
}
}

// New constructs a new JWTMiddleware instance with the supplied options.
func New(opts ...Option) *JWTMiddleware {
// New constructs a new JWTMiddleware instance with the supplied options. It
// requires a ValidateToken function to be passed in so it can properly
// validate tokens.
func New(validateToken ValidateToken, opts ...Option) *JWTMiddleware {
m := &JWTMiddleware{
validateToken: func(string) (interface{}, error) { panic("not implemented") },
validateToken: validateToken,
errorHandler: DefaultErrorHandler,
credentialsOptional: false,
tokenExtractor: AuthHeaderTokenExtractor,
Expand Down Expand Up @@ -228,7 +230,7 @@ func (m *JWTMiddleware) CheckJWT(next http.Handler) http.Handler {
}

// validate the token using the token validator
validToken, err := m.validateToken(token)
validToken, err := m.validateToken(r.Context(), token)
if err != nil {
m.errorHandler(w, r, &invalidError{details: err})
return
Expand Down
Loading

0 comments on commit 665e7da

Please sign in to comment.