jwt

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2021 License: MIT Imports: 8 Imported by: 0

README

janice-jwt

Build Status codecov Go Report Card

janice-jwt provides JWT token middleware for use with Janice. It uses jwt-go for token parsing and is similar in approach to go-jwt-middleware but as a janice.MiddlewareFunc implementation.

Getting started

go get -u github.com/stevecallear/janice-jwt
package main

import (
	"fmt"
	"net/http"

	jwt "github.com/stevecallear/janice-jwt"
)

var key = []byte("secretkey")

func main() {
	auth := jwt.New(jwt.HMAC(key))

	handler := auth.Then(func(w http.ResponseWriter, r *http.Request) error {
		c, _ := jwt.GetClaims(r)
		_, err := fmt.Fprintf(w, c["sub"].(string))
		return err
	})

	http.ListenAndServe(":8080", handler)
}

Token extraction

By default the middleware is configured to extract a bearer token from the request authorization header. This behaviour can be customised by replacing the options TokenFn. For example:

auth := jwt.New(jwt.HMAC(key), func(opt *jwt.Options) {
    opt.TokenFn = func(r *http.Request) (string, bool) {
        // return the extracted token and true if successful
    }
})

Signing methods

The module supports HMAC and RSA signing keys using jwt.HMAC and jwt.RSA respectively. Other signing methods are supported by replacing the options KeyFn. For example, the following returns an ECDSA signing key:

// import jwtgo "github.com/dgrijalva/jwt-go"

auth := jwt.New(func(o *jwt.Options) {
    o.KeyFn = func(_ *http.Request, t *jwtgo.Token) (interface{}, error) {
        // validate the signing method
        if _, ok := t.Method.(*jwtgo.SigningMethodECDSA); !ok {
            return nil, fmt.Errorf("invalid signing method: %s", t.Header["alg"])
        }

        return key, nil
    }
})

The requests is included in the KeyFn signature to support request-specific keys.

Error handling

Any errors extracting or parsing the token are handled by ErrorFn. By default this writes http.StatusUnauthorized to the response and returns nil. The behaviour can be customised to add logging or use error handling. The below example uses strudel to log and handle unauthorized errors:

auth := jwt.New(jwt.HMAC(key), func(opt *jwt.Options) {
    opt.ErrorFn = func(_ http.ResponseWriter, err error) error {
        return strudel.NewError(http.StatusText(http.StatusUnauthorized)).
            WithCode(http.StatusUnauthorized).
            WithLogField("err", err)
    }
})

chain := janice.New(strudel.ErrorHandling, auth)

handler := chain.Then(func(w http.ResponseWriter, r *http.Request) error {
    return nil
})

Anonymous requests

Anonymous requests can be permitted using the jwt.Optional option. This ensures that the request will be handled if TokenFn does not return a value. If a token is returned, but cannot be parsed then ErrorFn will be invoked as per standard behaviour. For example:

auth := jwt.New(jwt.RSA(key), jwt.Optional)

handler := auth.Then(func(w http.ResponseWriter, r *http.Request) error {
    var err error

    if c, ok := jwt.GetClaims(r); ok {
        _, err = fmt.Fprint(w, c["sub"].(string))
    } else {
        _, err = fmt.Fprint(w, "anonymous")
    }

    return err
})

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetClaims

func GetClaims(r *http.Request) (map[string]interface{}, bool)

GetClaims returns the claims for the specified request context

func HMAC

func HMAC(k []byte) func(*Options)

HMAC configures the middleware to use the specified HMAC key

func New

func New(fns ...func(*Options)) janice.MiddlewareFunc

New returns new JWT middleware for the specified option funcs

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"time"

	jwtgo "github.com/golang-jwt/jwt"
	jwt "github.com/stevecallear/janice-jwt"
)

func main() {
	key := []byte("secretkey")

	claims := map[string]interface{}{
		"sub": "test@email.com",
		"exp": time.Now().UTC().Add(1 * time.Hour),
	}

	token, err := jwtgo.NewWithClaims(jwtgo.SigningMethodHS512, jwtgo.MapClaims(claims)).SignedString(key)
	if err != nil {
		panic(err)
	}

	auth := jwt.New(jwt.HMAC(key))

	handler := auth.Then(func(w http.ResponseWriter, r *http.Request) error {
		c, _ := jwt.GetClaims(r)
		_, err := fmt.Fprint(w, c["sub"].(string))
		return err
	})

	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Add("Authorization", "Bearer "+token)

	handler.ServeHTTP(rec, req)

	fmt.Printf("%d %s", rec.Code, rec.Body)
}
Output:

200 test@email.com

func Optional

func Optional(o *Options)

Optional configures the middleware to allow unauthorized requests

func RSA

func RSA(k *rsa.PublicKey) func(*Options)

RSA configures the middleware to use the specified RSA PEM key

func WithClaims

func WithClaims(r *http.Request, c map[string]interface{}) *http.Request

WithClaims returns a copy of the request with the specified claims stored in the context The function is exported to simplify testing for apps that use GetClaims

Types

type Options

type Options struct {
	Optional bool
	TokenFn  func(*http.Request) (string, bool)
	KeyFn    func(*http.Request, *jwtgo.Token) (interface{}, error)
	ErrorFn  func(http.ResponseWriter, error) error
}

Options represents a set of middleware options

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL