thermokarst/jwt


A golang BYOL net/http middleware for working with JSON Web Tokens

  • Users starred: 16
  • Users forked: 4
  • Users watching: 16
  • Updated at: 2019-08-19 23:15:14

jwt

GoDoc

A simple (bring your own logic), opinionated Go net/http middleware for integrating JSON Web Tokens into your application:

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/thermokarst/jwt"
)

func protectMe(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "secured")
}

func dontProtectMe(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "not secured")
}

func auth(email string, password string) error {
	// Hard-code a user
	if email != "test" || password != "test" {
		return errors.New("invalid credentials")
	}
	return nil
}

func setClaims(id string) (map[string]interface{}, error) {
	currentTime := time.Now()
	return map[string]interface{}{
		"iat": currentTime.Unix(),
		"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
	}, nil
}

func verifyClaims(claims []byte, r *http.Request) error {
	currentTime := time.Now()
	var c struct {
		Iat int64
		Exp int64
	}
	_ = json.Unmarshal(claims, &c)
	if currentTime.After(time.Unix(c.Exp, 0)) {
		return errors.New("this token has expired")
	}
	return nil
}

func main() {
	config := &jwt.Config{
		Secret: "password",
		Auth:   auth,
		Claims: setClaims,
	}

	j, err := jwt.New(config)
	if err != nil {
		panic(err)
	}

	protect := http.HandlerFunc(protectMe)
	dontProtect := http.HandlerFunc(dontProtectMe)

	http.Handle("/authenticate", j.Authenticate())
	http.Handle("/secure", j.Secure(protect, verifyClaims))
	http.Handle("/insecure", dontProtect)
	http.ListenAndServe(":8080", nil)
}
$ http GET :8080/secure

    HTTP/1.1 401 Unauthorized
    Content-Length: 23
    Content-Type: text/plain; charset=utf-8
    Date: Fri, 08 May 2015 06:43:35 GMT

    please provide a token

$ http POST :8080/authenticate email=test password=test

    HTTP/1.1 200 OK
    Content-Length: 130
    Content-Type: text/plain; charset=utf-8
    Date: Fri, 08 May 2015 06:31:42 GMT

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos=

$ http GET :8080/secure Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos="

    HTTP/1.1 200 OK
    Content-Length: 7
    Content-Type: text/plain; charset=utf-8
    Date: Fri, 08 May 2015 06:38:30 GMT

    secured

Installation

$ go get github.com/thermokarst/jwt

Usage

This is a work in progress

Create a new instance of the middleware by passing in a configuration for your app. The config includes a shared secret (this middleware only builds HS256 tokens), a function for authenticating user, and a function for generating a user's claims. The idea here is to be dead-simple for someone to drop this into a project and hit the ground running.

config := &jwt.Config{
    Secret: "password",
    Auth:   authFunc, // func(string, string) error
    Claims: claimsFunc, // func(string) (map[string]interface{})
}
j, err := jwt.New(config)

You can also customize the field names by specifying IdentityField and VerifyField in the Config struct, if you want the credentials to be something other than "email" and "password".

Once the middleware is instantiated, create a route for users to generate a JWT at.

http.Handle("/authenticate", j.Authenticate())

The auth function takes two arguments (the identity, and the authorization key), POSTed as a JSON-encoded body:

{"email":"user@example.com","password":"mypassword"}

These fields are static for now, but will be customizable in a later release. The claims are generated using the claims function provided in the configuration. This function is only run if the auth function verifies the user's identity, then the user's unique identifier (primary key id, UUID, email, whatever you want) is passed as a string to the claims function. Your function should return a map[string]interface{} with the desired claimset.

Routes are "secured" by calling the Secure(http.Handler, jwt.VerifyClaimsFunc) handler:

http.Handle("/secureendpoint", j.Secure(someHandler, verifyClaimsFunc))

The claims verification function is called after the token has been parsed and validated: this is where you control how your application handles the claims contained within the JWT.

Motivation

This work was prepared for a crypto/security class at the University of Alaska Fairbanks. I hope to use this in some of my projects, but please proceed with caution if you adopt this for your own work. As well, the API is still quite unstable, so be prepared for handling any changes.

Tests

$ go test

Contributors

Matthew Ryan Dillon (matthewrdillon@gmail.com)