func (auth *Authenticator) EncodeToken(w http.ResponseWriter, cs *jwt.ClaimSet) error {
	expires := time.Now().Add(auth.CookieLifespan)
	err := cs.Set("exp", expires.Unix())
	if err != nil {
		return fmt.Errorf("can't set exp value: %s", err)
	}
	err = cs.Set("iat", time.Now().Unix())
	if err != nil {
		return fmt.Errorf("can't set iat value: %s", err)
	}
	jti := int64(auth.SerialGen.Generate())
	err = cs.Set("jti", strconv.FormatInt(jti, jtiNumericBase))
	if err != nil {
		return fmt.Errorf("token jti construction error: %s", err)
	}
	ntok, err := cs.MarshalJSON()
	if err != nil {
		return fmt.Errorf("token marshalling error: %s", err)
	}
	enc, err := jwe.Encrypt(ntok, jwa.RSA_OAEP_256, &auth.PrivateKey.PublicKey, jwa.A128CBC_HS256, jwa.Deflate)
	if err != nil {
		return fmt.Errorf("token encryption error: %s", err)
	}
	cookie := &http.Cookie{
		Name:     auth.CookieName,
		Value:    string(enc),
		Expires:  expires,
		MaxAge:   int(auth.CookieLifespan.Seconds()),
		HttpOnly: true,
		Path:     "/",
	}
	http.SetCookie(w, cookie)
	return nil
}
EncodeToken encodes a jwt.ClaimSet into a cookie and sends it to the
browser client.
package jwtauth
import (
	"context"
	"crypto/rsa"
	"fmt"
	"net/http"
	"strconv"
	"time"
	"github.com/lestrrat/go-jwx/jwa"
	"github.com/lestrrat/go-jwx/jwe"
	"github.com/lestrrat/go-jwx/jwt"
	"github.com/lpar/serial"
)
type Authenticator struct {
	CookieName     string         
	CookieLifespan time.Duration  
	ContextName    string         
	LoginURL       string         
	PrivateKey     *rsa.PrivateKey
	SerialGen      *serial.Generator
	GC             chan struct{}  
}
const defaultCookieName = "token"
const defaultCookieLifespan = time.Hour / 3
const defaultContextName = "jwtauth"
const defaultLoginURL = "/login"
func NewAuthenticator(rsakey *rsa.PrivateKey) *Authenticator {
	auth := &Authenticator{
		CookieName:     defaultCookieName,
		CookieLifespan: defaultCookieLifespan,
		ContextName:    defaultContextName,
		LoginURL:       defaultLoginURL,
		SerialGen:      serial.NewGenerator(),
		PrivateKey:     rsakey,
	}
	return auth
}
func (auth *Authenticator) StartGC() {
	auth.GC = auth.NewGarbageCollector()
}
func (auth *Authenticator) StopGC() {
	close(auth.GC)
}
const jtiNumericBase = 36
func (auth *Authenticator) Logout(next ...http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		cookie := &http.Cookie{
			Name:     auth.CookieName,
			Value:    "",
			MaxAge:   -1,
			HttpOnly: true,
			Path:     "/",
		}
		http.SetCookie(w, cookie)
		if len(next) == 0 {
			http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
			return
		}
		next[0].ServeHTTP(w, r)
	})
}
func (auth *Authenticator) decodeToken(r *http.Request) (*jwt.ClaimSet, error) {
	cs := jwt.NewClaimSet()
	ctok, err := r.Cookie(auth.CookieName)
	if err != nil {
		return cs, fmt.Errorf("no token cookie '%s'", auth.CookieName)
	}
	stok := ctok.Value
	dec, err := jwe.Decrypt([]byte(stok), jwa.RSA_OAEP_256, auth.PrivateKey)
	if err != nil {
		return cs, err
	}
	err = cs.UnmarshalJSON(dec)
	if err != nil {
		return cs, err
	}
	sub := cs.Get("sub").(string)
	if sub == "" {
		return cs, fmt.Errorf("empty claimset (no subject)")
	}
	tokex := cs.Get("exp").(int64)
	if tokex < time.Now().Unix() {
		return cs, fmt.Errorf("token for %s expired at %s", cs.Get("sub"),
			time.Unix(tokex, 0))
	}
	jti := cs.Get("jti").(string)
	if jti == "" {
		return cs, fmt.Errorf("token with no jti")
	}
	jtiint, err := strconv.ParseInt(jti, jtiNumericBase, 64)
	if err != nil {
		return cs, fmt.Errorf("garbage jti in token: %s", jti)
	}
	if auth.SerialGen.Seen(serial.Serial(jtiint)) {
		return cs, fmt.Errorf("attempt to reuse token: %s", jti)
	}
	auth.SerialGen.SetSeen(serial.Serial(jtiint))
	return cs, nil
}
func (auth *Authenticator) NewGarbageCollector() chan struct{} {
	ticker := time.NewTicker(auth.CookieLifespan / 2)
	quit := make(chan struct{})
	go func() {
		for {
			select {
			case <-ticker.C:
				auth.SerialGen.ExpireSeen(auth.CookieLifespan)
			case <-quit:
				ticker.Stop()
				return
			}
		}
	}()
	return quit
}