jwt

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 22, 2018 License: CC0-1.0 Imports: 15 Imported by: 0

README

API Documentation Build Status Test Coverage

A JSON Web Token (JWT) library for the Go programming language.

The API enforces secure use by design. Unsigned tokens are rejected and there is no support for (ECDSA) encryption—use wire encryption instead. With less than 500 lines of code and no third party dependencies, the implementation maintains full unit test coverage.

This is free and unencumbered software released into the public domain.

Get Started

The package comes with functions to verify [HMACCheck, RSACheck] and issue [HMACSign, RSASign] claims.

For server side security an http.Handler based setup can be used as well. The following example enforces the subject, formatted name and roles to be present as a valid JWT in all requests towards the MyAPI handler.

// configuration demo
http.DefaultServeMux.Handle("/api/v1", &jwt.Handler{
	Target: MyAPI, // the protected service multiplexer
	RSAKey: JWTPublicKey,

	// map some claims to HTTP headers
	HeaderBinding: map[string]string{
		"sub": "X-Verified-User", // registered [standard] claim
		"fn":  "X-Verified-Name", // private [custom] claim
	},

	// customise further with RBAC
	Func: func(w http.ResponseWriter, req *http.Request, claims *jwt.Claims) (pass bool) {
		log.Printf("got a valid JWT %q for %q", claims.ID, claims.Audience)

		// map role enumeration
		s, ok := claims.String("roles")
		if !ok {
			http.Error(w, "jwt: want roles claim as a string", http.StatusForbidden)
			return false
		}
		req.Header["X-Verified-Roles"] = strings.Fields(s)

		return true
	},
})

When all applicable JWT claims are mapped to HTTP request headers, then the service logic can stay free of verification code plus easier unit testing.

// Greeting is a standard HTTP handler fuction.
func Greeting(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Hello %s!\nYou are authorized as %s.\n",
		req.Header.Get("X-Verified-Name"), req.Header.Get("X-Verified-User"))
}

Optionally one can use the claims object in the service handlers as shown in the “direct” example.

Performance on a Mac Pro (late 2013)

BenchmarkHMACSign/HS256-12         	  500000	      3497 ns/op
BenchmarkHMACSign/HS384-12         	  300000	      4090 ns/op
BenchmarkHMACSign/HS512-12         	  300000	      4192 ns/op
BenchmarkHMACCheck/HS256-12        	  200000	      7088 ns/op
BenchmarkHMACCheck/HS384-12        	  200000	      7807 ns/op
BenchmarkHMACCheck/HS512-12        	  200000	      7939 ns/op
BenchmarkRSASign/1024-bit-12       	    3000	    569604 ns/op
BenchmarkRSASign/2048-bit-12       	     500	   2569394 ns/op
BenchmarkRSASign/4096-bit-12       	     100	  14744651 ns/op
BenchmarkRSACheck/1024-bit-12      	   50000	     33513 ns/op
BenchmarkRSACheck/2048-bit-12      	   20000	     73952 ns/op
BenchmarkRSACheck/4096-bit-12      	   10000	    204450 ns/op

JWT.io

Documentation

Overview

Package jwt implements "JSON Web Token (JWT)" RFC 7519. Signatures only; no unsecured nor encrypted tokens.

Example
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"

	"github.com/pascaldekloe/jwt"
)

// JWTSecret is the HMAC key.
var JWTSecret = []byte("guest")

func main() {
	// run secured service
	srv := httptest.NewTLSServer(&jwt.Handler{
		Target: http.HandlerFunc(Greeting),
		Secret: JWTSecret,
		HeaderBinding: map[string]string{
			"sub": "X-Verified-User", // registered [standard] claim name
			"fn":  "X-Verified-Name", // private [custom] claim name
		},
	})
	defer srv.Close()

	// build request with claims
	claims := &jwt.Claims{
		Registered: jwt.Registered{
			Subject: "lakane",
		},
		Set: map[string]interface{}{
			"fn": "Lana Anthony Kane",
		},
	}
	req, err := http.NewRequest("GET", srv.URL, nil)
	if err != nil {
		panic(err)
	}
	if err := claims.HMACSignHeader(req, jwt.HS512, JWTSecret); err != nil {
		panic(err)
	}

	// call service
	resp, err := srv.Client().Do(req)
	if err != nil {
		panic(err)
	}
	fmt.Println("HTTP", resp.Status)
	io.Copy(os.Stdout, resp.Body)
}

// Greeting is a standard HTTP handler fuction.
func Greeting(w http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(w, "Hello %s!\nYou are authorized as %s.\n",
		req.Header.Get("X-Verified-Name"), req.Header.Get("X-Verified-User"))
}
Output:

HTTP 200 OK
Hello Lana Anthony Kane!
You are authorized as lakane.

Index

Examples

Constants

View Source
const (
	HS256 = "HS256" // HMAC SHA-256
	HS384 = "HS384" // HMAC SHA-384
	HS512 = "HS512" // HMAC SHA-512
	RS256 = "RS256" // RSASSA-PKCS1-v1_5 with SHA-256
	RS384 = "RS384" // RSASSA-PKCS1-v1_5 with SHA-348
	RS512 = "RS512" // RSASSA-PKCS1-v1_5 with SHA-512
)

Algorithm Identification Tokens

View Source
const MIMEType = "application/jwt"

MIMEType is the IANA registered media type.

View Source
const OAuthURN = "urn:ietf:params:oauth:token-type:jwt"

OAuthURN is the IANA registered OAuth URI.

Variables

View Source
var ErrAlgUnk = errors.New("jwt: algorithm unknown")

ErrAlgUnk signals an unsupported "alg" token (for the respective method).

View Source
var ErrSigMiss = errors.New("jwt: signature mismatch")

ErrSigMiss means the signature check failed.

View Source
var ErrUnsecured = errors.New("jwt: unsecured—no signature")

ErrUnsecured signals the "none" algorithm.

HMACAlgs is the HMAC hash algorithm registration. When adding additional entries you also need to import the respective packages to link the hash function into the binary crypto.Hash.Available.

RSAAlgs is the RSA hash algorithm registration. When adding additional entries you also need to import the respective packages to link the hash function into the binary crypto.Hash.Available.

Functions

This section is empty.

Types

type Claims

type Claims struct {
	// Registered field values take precedence.
	Registered

	// Raw has the JSON payload. This field is read-only.
	Raw json.RawMessage

	// Set has the claims set mapped by name for non-standard usecases.
	// Use Registered fields when possible.
	Set map[string]interface{}
}

Claims is JWT payload representation.

func HMACCheck

func HMACCheck(jwt, secret []byte) (*Claims, error)

HMACCheck parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. When the algorithm is not in HMACAlgs then the error is ErrAlgUnk. See Valid to complete the verification.

func HMACCheckHeader

func HMACCheckHeader(r *http.Request, secret []byte) (*Claims, error)

HMACCheckHeader applies HMACCheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func RSACheck

func RSACheck(jwt []byte, key *rsa.PublicKey) (*Claims, error)

RSACheck parses a JWT and returns the claims set if, and only if, the signature checks out. Note that this excludes unsecured JWTs ErrUnsecured. When the algorithm is not in RSAAlgs then the error is ErrAlgUnk. See Valid to complete the verification.

func RSACheckHeader

func RSACheckHeader(r *http.Request, key *rsa.PublicKey) (*Claims, error)

RSACheckHeader applies RSACheck on a HTTP request. Specifically it looks for a bearer token in the Authorization header.

func (*Claims) HMACSign

func (c *Claims) HMACSign(alg string, secret []byte) (token []byte, err error)

HMACSign calls Sync and returns a new JWT. When the algorithm is not in HMACAlgs then the error is ErrAlgUnk.

func (*Claims) HMACSignHeader

func (c *Claims) HMACSignHeader(r *http.Request, alg string, secret []byte) error

HMACSignHeader applies HMACSign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) Number

func (c *Claims) Number(name string) (value float64, ok bool)

Number returns the claim when present and if the representation is a JSON number.

func (*Claims) RSASign

func (c *Claims) RSASign(alg string, key *rsa.PrivateKey) (token []byte, err error)

RSASign calls Sync and returns a new JWT. When the algorithm is not in RSAAlgs then the error is ErrAlgUnk.

func (*Claims) RSASignHeader

func (c *Claims) RSASignHeader(r *http.Request, alg string, key *rsa.PrivateKey) error

RSASignHeader applies RSASign on a HTTP request. Specifically it sets a bearer token in the Authorization header.

func (*Claims) String

func (c *Claims) String(name string) (value string, ok bool)

String returns the claim when present and if the representation is a JSON string.

func (*Claims) Sync

func (c *Claims) Sync() error

Sync updates the Raw field and when the Set field is not nil then all non-zero Registered values are copied into the map accordingly.

func (*Claims) Valid

func (c *Claims) Valid(t time.Time) bool

Valid returns whether the claims set may be accepted for processing at the given moment in time.

type Handler

type Handler struct {
	// Target is the secured service.
	Target http.Handler

	// Secret is the HMAC key.
	Secret []byte
	// RSAKey applies RSAAlgs and disables HMACAlgs when set.
	RSAKey *rsa.PublicKey

	// HeaderBinding maps JWT claim names to HTTP header names.
	// All requests passed to Target have these headers set. In
	// case of failure the request is rejected with status code
	// 401 (Unauthorized) and a description.
	HeaderBinding map[string]string

	// Func is called after the JWT validation succeeds and
	// before any header bindings. Requests are dropped when
	// the return is false.
	Func func(http.ResponseWriter, *http.Request, *Claims) (pass bool)
}

Handler protects an http.Handler with security enforcements. Requests are passed to Target only when the JWT checks out.

Example (Direct)
package main

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

	"github.com/pascaldekloe/jwt"
)

func main() {
	h := &jwt.Handler{
		Target: nil,
		Secret: []byte("killarcherdie"),

		// use as http.HandlerFunc with JWT argument
		Func: func(w http.ResponseWriter, req *http.Request, claims *jwt.Claims) (pass bool) {
			if n, ok := claims.Number("deadline"); !ok {
				fmt.Fprintln(w, "you don't have a deadline")
			} else {
				t := jwt.NumericTime(n)
				fmt.Fprintln(w, "deadline at", t.String())
			}
			return // false stops processing
		},
	}

	req := httptest.NewRequest("GET", "/status", nil)
	req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.eyJkZWFkbGluZSI6NjcxNTAwNzk5fQ.yeUUNOj4-RvNp5Lt0d3lpS7MTgsS_Uk9XnsXJ3kVLhw")
	resp := httptest.NewRecorder()
	h.ServeHTTP(resp, req)
	fmt.Printf("HTTP %d: %s", resp.Code, resp.Body)
}
Output:

HTTP 200: deadline at 1991-04-12T23:59:59Z

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP honors the http.Handler interface.

type NumericTime

type NumericTime float64

NumericTime is a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds.

func NewNumericTime

func NewNumericTime(t time.Time) *NumericTime

NewNumericTime returns the the corresponding representation with nil for the zero value.

func (*NumericTime) String

func (n *NumericTime) String() string

String returs the ISO representation.

func (*NumericTime) Time

func (n *NumericTime) Time() time.Time

Time returns the Go mapping with the zero value for nil.

type Registered

type Registered struct {
	// Issuer identifies the principal that issued the JWT.
	Issuer string `json:"iss,omitempty"`

	// Subject identifies the principal that is the subject of the JWT.
	Subject string `json:"sub,omitempty"`

	// Audience identifies the recipients that the JWT is intended for.
	Audience string `json:"aud,omitempty"`

	// Expires identifies the expiration time on or after which the JWT
	// must not be accepted for processing.
	Expires *NumericTime `json:"exp,omitempty"`

	// NotBefore identifies the time before which the JWT must not be
	// accepted for processing.
	NotBefore *NumericTime `json:"nbf,omitempty"`

	// Issued identifies the time at which the JWT was issued.
	Issued *NumericTime `json:"iat,omitempty"`

	// ID provides a unique identifier for the JWT.
	ID string `json:"jti,omitempty"`
}

Registered are the IANA registered "JSON Web Token Claims".

Jump to

Keyboard shortcuts

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