Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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