jwthelper

package module
v0.0.0-...-bb90eab Latest Latest
Warning

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

Go to latest
Published: May 14, 2018 License: MIT Imports: 9 Imported by: 1

README

jwthelper

Build Status Go Report Card GoDoc

Package jwthelper provides JWT(JSON Web Token) functions based on jwt-go.

Documentation
How to Generate Keys for JWT algs
Thanks
License

Documentation

Overview

Package jwthelper provides JWT(JSON Web Token) functions based on jwt-go.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidMultipleKeysParser = fmt.Errorf("invalid multiple keys parser")
	ErrKIDNotFound               = fmt.Errorf("kid not found in claims")
	ErrKIDType                   = fmt.Errorf("invalid kid type(not string)")
	ErrParserNotFound            = fmt.Errorf("parser not found by kid")
)
View Source
var (
	ErrInvalidMultipleKeysSigner = fmt.Errorf("invalid multiple keys signer")
	ErrSignerNotFound            = fmt.Errorf("signer not found by given kid(key id)")
)
View Source
var (
	// ErrInvalidParser represents the error of invalid parser.
	ErrInvalidParser = fmt.Errorf("invalid parser")
	// ErrParseClaims represents the error of failed to parse claims.
	ErrParseClaims = fmt.Errorf("failed to parse claims")
	// ErrInvalidToken represents the error of invalid token.
	ErrInvalidToken   = fmt.Errorf("invalid token")
	ErrInvalidPartNum = fmt.Errorf("invalid number of JWT part")
)
View Source
var (
	// ErrInvalidSigningMethod is the error of invalid signing method.
	ErrInvalidSigningMethod = fmt.Errorf("invalid signing method")
	// ErrInvalidSigner is the error of invalid signer.
	ErrInvalidSigner = fmt.Errorf("invalid signer")
	// ErrInvalidAlg is the error of invalid alg.
	ErrInvalidAlg = fmt.Errorf("invalid alg")
)

Functions

func NewCookie

func NewCookie(tokenString string, options ...CookieOption) *http.Cookie

NewCookie news a cookie contains JWT token.

Params:
    tokenString: JWT token string. It'll be set as cookie value.
    options: Cookie options(optional).
             Use helper functions to get options: CookieName(), CookieDomain()...
Comments:
    It'll set cookie name to "jwt" if no name option specified.
Example
package main

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/northbright/jwthelper"
)

func doPostRequest(URL string) *http.Cookie {
	v := url.Values{}
	v.Set("username", "admin")
	v.Set("password", "admin")

	// Values.Encode() encodes the values into "URL encoded" form sorted by key.
	s := v.Encode()

	req, err := http.NewRequest("POST", URL, strings.NewReader(s))
	if err != nil {
		log.Printf("NewRequest error: %v", err)
		return nil
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	c := &http.Client{}
	resp, err := c.Do(req)
	if err != nil {
		log.Printf("Do() error: %v", err)
		return nil
	}
	defer resp.Body.Close()

	// Get JWT cookie("jwt").
	cookies := resp.Cookies()
	for _, cookie := range cookies {
		if cookie.Name == "jwt" {
			log.Printf("After POST, JWT cookie: %v, resp: %v", cookie, resp)
			return cookie
		}
	}
	return nil
}

func doGetRequest(URL string, cookie *http.Cookie) {
	req, err := http.NewRequest("GET", URL, nil)
	if err != nil {
		log.Printf("NewRequest error: %v", err)
		return
	}
	// Add JWT cookie return by POST.
	req.AddCookie(cookie)

	c := &http.Client{}
	resp, err := c.Do(req)
	if err != nil {
		log.Printf("Do() error: %v", err)
		return
	}
	defer resp.Body.Close()

	// Get response("admin").
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("ReadAll() error: %v", err)
		return
	}
	log.Printf("GET response: %v", string(buf))
}

func login(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		cookie, err := r.Cookie("jwt")
		if err != nil {
			log.Printf("get JWT cookie error: %v", err)
			return
		}

		tokenString := cookie.Value
		parser, err := jwthelper.NewParser("RS256", []byte(rsaPubPEM))
		if err != nil {
			log.Printf("NewParser() error: %v", err)
			return
		}

		m, err := parser.Parse(tokenString)
		if err != nil {
			log.Printf("parser.Parse() error: %v", err)
			return
		}
		fmt.Fprintf(w, "hello, %v!", m["username"])

	case "POST":
		// Call ParseForm() to parse the raw query and update r.PostForm and r.Form.
		if err := r.ParseForm(); err != nil {
			return
		}

		// Post form from website
		username := r.FormValue("username")
		password := r.FormValue("password")
		if username == "admin" && password == "admin" {
			signer, err := jwthelper.NewSigner("RS256", []byte(rsaPrivPEM))
			if err != nil {
				log.Printf("NewSigner() error: %v", err)
				return
			}

			tokenString, err := signer.SignedString(
				jwthelper.NewClaim("username", username),
			)
			if err != nil {
				return
			}
			cookie := jwthelper.NewCookie(tokenString)
			http.SetCookie(w, cookie)
			fmt.Fprintf(w, "POST")
		}
	default:
		fmt.Fprintf(w, "Sorry, only GET and POST methods are supported.")
	}
}

func shutdownServer(srv *http.Server) {
	log.Printf("shutdown server...")
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("shutdown server error: %v", err)
	}
	log.Println("shutdown server successfully")
}

func main() {
	log.Printf("\n\nExample of set / get JWT in cookie")

	mux := http.NewServeMux()
	mux.HandleFunc("/login", login)

	srv := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	go func() {
		time.Sleep(1 * time.Second)
		cookie := doPostRequest("http://localhost:8080/login")
		if cookie != nil {
			doGetRequest("http://localhost:8080/login", cookie)
		}
		shutdownServer(srv)
	}()

	err := srv.ListenAndServe()
	if err != nil {
		if err == http.ErrServerClosed {
			log.Printf("server has been closed")
		} else {
			log.Printf("ListenAndServe() error: %s", err)
		}
	}

}
Output:

func ParseClaims

func ParseClaims(tokenString string) (map[string]interface{}, error)

Types

type Claim

type Claim struct {
	// contains filtered or unexported fields
}

Claim represents JWT claim. Use claim helper functions to get a Claim: NewClaim(), TimeClaim()...

func NewClaim

func NewClaim(name string, value interface{}) Claim

NewClaim news a Claim with given name -> value pair.

func TimeClaim

func TimeClaim(name string, value time.Time) Claim

TimeClaim returns a Claim with time.Time value. It'll convert the time.Time to a Unix timestamp.

type CookieOption

type CookieOption struct {
	// contains filtered or unexported fields
}

CookieOption represent cookie option.

func CookieDomain

func CookieDomain(domain string) CookieOption

CookieDomain returns the option for cookie domain.

func CookieExpires

func CookieExpires(expires time.Time) CookieOption

CookieExpires returns the option for cookie expires.

func CookieHttpOnly

func CookieHttpOnly(httpOnly bool) CookieOption

CookieHttpOnly returns the option for HTTP only cookie.

func CookieMaxAge

func CookieMaxAge(maxAge int) CookieOption

CookieMaxAge returns the option for cookie max age.

func CookieName

func CookieName(name string) CookieOption

CookieName returns the option for cookie name. It'll use "jwt" as cookie name by default.

func CookiePath

func CookiePath(path string) CookieOption

CookiePath returns the option for cookie path.

func CookieSecure

func CookieSecure(secure bool) CookieOption

CookieSecure returns the option for secure cookie.

type MultipleKeysParser

type MultipleKeysParser struct {
	// contains filtered or unexported fields
}

func NewMultipleKeysParser

func NewMultipleKeysParser() *MultipleKeysParser

func (*MultipleKeysParser) Get

func (p *MultipleKeysParser) Get(kid string) *Parser

func (*MultipleKeysParser) Parse

func (p *MultipleKeysParser) Parse(tokenString string) (map[string]interface{}, error)

func (*MultipleKeysParser) Set

func (p *MultipleKeysParser) Set(kid string, parser *Parser)

func (*MultipleKeysParser) Valid

func (p *MultipleKeysParser) Valid() bool

type MultipleKeysSigner

type MultipleKeysSigner struct {
	// contains filtered or unexported fields
}

func NewMultipleKeysSigner

func NewMultipleKeysSigner() *MultipleKeysSigner

func (*MultipleKeysSigner) Get

func (s *MultipleKeysSigner) Get(kid string) *Signer

func (*MultipleKeysSigner) Set

func (s *MultipleKeysSigner) Set(kid string, signer *Signer)

func (*MultipleKeysSigner) SignedString

func (s *MultipleKeysSigner) SignedString(kid string, claims ...Claim) (string, error)
Example
package main

import (
	"encoding/json"
	"log"

	"github.com/northbright/jwthelper"
)

func main() {
	// Example to show sign / parse JWT with multiple keys.
	log.Printf("\n\nExample of sign / parse JWT with multiple keys")

	// New a signer with RSA SHA-384 alg by given RSA private PEM key.
	s2, err := jwthelper.NewSignerFromFile("RS384", "keys/rsa-priv-api.pem")
	if err != nil {
		log.Printf("NewSigner() error: %v", err)
		return
	}
	// New a multiple keys signer and set signers with "kid"(key id) which will be added to claims automatically.
	// We have RSA private key of API server but not have private key of vendor.
	signer := jwthelper.NewMultipleKeysSigner()
	signer.Set("kid-api", s2)

	tokenStrSignedByAPIKey, err := signer.SignedString(
		"kid-api",
		jwthelper.NewClaim("uid", "2"),
		jwthelper.NewClaim("count", 200),
	)

	if err != nil {
		log.Printf("SignedString() error: %v", err)
		return
	}
	log.Printf("SignedString() OK. str: %v", tokenStrSignedByAPIKey)

	// New parsers from public PEM file.
	p1, err := jwthelper.NewParserFromFile("RS512", "keys/rsa-pub-vendor.pem")
	if err != nil {
		log.Printf("NewParserFromFile() error: %v", err)
	}

	p2, err := jwthelper.NewParserFromFile("RS384", "keys/rsa-pub-api.pem")
	if err != nil {
		log.Printf("NewParserFromFile() error: %v", err)
	}

	// New a multiple keys parser and set parsers with "kid".
	parser := jwthelper.NewMultipleKeysParser()
	parser.Set("kid-vendor", p1)
	parser.Set("kid-api", p2)

	tokenStrs := []string{tokenStrSignedByAPIKey, tokenStrSignedByVendor}

	for _, tokenStr := range tokenStrs {
		mapClaims, err := parser.Parse(tokenStr)
		if err != nil {
			log.Printf("Parse() error: %v", err)
			return
		}

		uid, ok := mapClaims["uid"]
		if !ok {
			log.Printf("uid not found")
			return
		}

		if _, ok = uid.(string); !ok {
			log.Printf("uid is not string type")
			return
		}

		count, ok := mapClaims["count"]
		if !ok {
			log.Printf("count not found")
			return
		}

		// It'll parse number as json.Number type by default.
		// Call Number.Int64(), Number.Float64(), Number.String() according to your need.
		// See https://godoc.org/encoding/json#Number
		num, ok := count.(json.Number)
		if !ok {
			log.Printf("count is not json.Number type: %T", count)
			return
		}

		n, err := num.Int64()
		if err != nil {
			log.Printf("convert json.Number to int64 error: %v", err)
			return
		}

		log.Printf("Parse() OK. uid: %v, count: %v, mapClaims: %v", uid, n, mapClaims)
	}
}

// Token signed with RSA SHA-512 alg and vendor private key.
var tokenStrSignedByVendor string = `eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJjb3VudCI6MTAwLCJraWQiOiJraWQtdmVuZG9yIiwidWlkIjoiMSJ9.b5yqIYAeXMBpSexELGneELzSeCMWbKR_vUDaLiZvmWEv69GrHkytGDk1U-FjxUmoIU7-o8_qyh0StTV-R5okckChWdcdH5hWPIvgbxhI2uIHg4gVk3-BGdJn4nZAYNrk0CkUt-apvSH_0WZA8wlDcGRglpsWmqbD2X0k35VMLoA_boQsK6xzP2cHT3LHUcLVxE9pzC2kKxNho8wgDk9g76EPQ5S0ynso08lFDxOW7K1i8bOq6ZCfnzr98pMNlbcP-AuVqMqG94Ni1qpClnJXZ66CusVQ-cy-2eSnPaZkvlcPTZiQcBNZTPaf09vOXKaqbzWB1zHImbRiAi3EPYktnw`
Output:

func (*MultipleKeysSigner) Valid

func (s *MultipleKeysSigner) Valid() bool

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser is used to parse JWT token string.

func NewParser

func NewParser(alg string, key []byte, options ...ParserOption) (*Parser, error)

NewParser creates a parser with given "alg"(RFC7518) and verifying key.

alg: See: https://tools.ietf.org/html/rfc7518#section-3.1 "none" alg is not supported. key: use random bytes as key for "HS256", "HS384", "HS512". use public PEM string as key for "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512".

func NewParserFromFile

func NewParserFromFile(alg string, f string, options ...ParserOption) (*Parser, error)

NewParserFromFile creates a parser with given "alg"(RFC7518) and verifying key file.

func (*Parser) Parse

func (p *Parser) Parse(tokenString string) (map[string]interface{}, error)

Parse parses the signed string and returns the map which stores claims.

tokenString: token string to be parsed. Return: map stores claims. comments: by default, ParserUseJSONNumber option is true. all numbers will be parsed to json.Number type. Use Number.Int64(), Number.Float64(), Number.String() according to your need. You may get float64 type if set ParserUseJSONNumber option to false when new a parser.

func (*Parser) Valid

func (p *Parser) Valid() bool

Valid validates the parser.

type ParserOption

type ParserOption struct {
	// contains filtered or unexported fields
}

ParserOption represents the option for parsing JWT token string. Use option helper functions to set options: e.g. ParserUseJSONNumber()

func ParserUseJSONNumber

func ParserUseJSONNumber(flag bool) ParserOption

ParserUseJSONNumber returns the option for using JSON number. It causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64. After calling Parser.Parse(), the type of number stored in the map[string]interface{} is: * float64: flag is false. * json.Number: flag is true. See https://godoc.org/encoding/json#Decoder.UseNumber

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer is used to sign JWT tokens. It stores signing method and key internally.

func NewSigner

func NewSigner(alg string, key []byte) (*Signer, error)

NewSigner creates a signer with given "alg"(RFC7518) and signing key.

alg: See: https://tools.ietf.org/html/rfc7518#section-3.1 "none" alg is not supported. key: use random bytes as key for "HS256", "HS384", "HS512". use private PEM string as key for "RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512".

func NewSignerFromFile

func NewSignerFromFile(alg string, f string) (*Signer, error)

NewSignerFromFile creates a signer with given "alg"(RFC7518) and signing key file.

func (*Signer) SignedString

func (s *Signer) SignedString(claims ...Claim) (string, error)

SignedString returns the signed string of the JWT token with given claims.

claims: variadic Claim returned by claim helper functions. e.g. NewClaim("name", "frank"), NewClaim("count", 100) Return: signed string of JWT token.

Example
package main

import (
	"encoding/json"
	"log"

	"github.com/northbright/jwthelper"
)

func main() {
	log.Printf("\n\nExample of Signer / Parser")

	// New a signer with RSA SHA-256 alg by given RSA private PEM key.
	s, err := jwthelper.NewSigner("RS256", []byte(rsaPrivPEM))
	if err != nil {
		log.Printf("NewSigner() error: %v", err)
		return
	}

	// Pass claim... to SignedString().
	str, err := s.SignedString(
		jwthelper.NewClaim("uid", "1"),
		jwthelper.NewClaim("count", 100),
	)

	if err != nil {
		log.Printf("SignedString() error: %v", err)
		return
	}
	log.Printf("SignedString() OK. str: %v", str)

	// New a parser with RSA SHA-256 alg by given RSA public PEM key.
	p, err := jwthelper.NewParser("RS256", []byte(rsaPubPEM))
	if err != nil {
		log.Printf("NewParser() error: %v", err)
		return
	}

	mapClaims, err := p.Parse(str)
	if err != nil {
		log.Printf("Parse() error: %v", err)
		return
	}

	uid, ok := mapClaims["uid"]
	if !ok {
		log.Printf("uid not found")
		return
	}

	if _, ok = uid.(string); !ok {
		log.Printf("uid is not string type")
		return
	}

	count, ok := mapClaims["count"]
	if !ok {
		log.Printf("count not found")
		return
	}

	// It'll parse number as json.Number type by default.
	// Call Number.Int64(), Number.Float64(), Number.String() according to your need.
	// See https://godoc.org/encoding/json#Number
	num, ok := count.(json.Number)
	if !ok {
		log.Printf("count is not json.Number type: %T", count)
		return
	}

	n, err := num.Int64()
	if err != nil {
		log.Printf("convert json.Number to int64 error: %v", err)
		return
	}

	log.Printf("Parse() OK. uid: %v, count: %v, mapClaims: %v", uid, n, mapClaims)

}

var rsaPrivPEM string = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAn6MgLVWPxXqLfMRoCS2tXcRiJn/q+0h+Y2cNw0U0lQ6dIL5W
lFhr0C8YPHLDiGxe2AzMG0jj7QAvZnBKIQUA60WRoQ4MhS0mb66nqSZvPPfX74FN
Cdy7e0inW9CexBFKhW/UTI0PjF4Dl/fFdo5hcPTgeaPsiWWMoKVFdgDBfjYAnJvD
BzqYJfIZ61LrqIxrvHxmQ6ZoiLBc6ku2o6eHNYmwfMM82nQWrqPNZVCcCSQtD7+C
FiP4uNlTXIP9W436sDx+EsHI1HwEPFZA7Eb8shTV5s6Z4tfYYTs5873U2OF6DLCp
pOwSy2bvBzGamib9icZnXIkOv9v9Vf13lEhNAQIDAQABAoIBAFv1I/v5ZbBkPyXI
HgXrggqZrdBvr3TA9c1c99icbQXQPUM3Ybhilvh9qIBpu6lChAAAnzK4clN739Iq
rQkIUNc2ZAVaimvM7m83NO2DbmC4hHM7EJ21wWnrGD0Tl+Fp9HuZR7oxJ9u77GYG
HIGG0yq2ZPitLPyYusFvcuve05dXq2O+/RwQvmZ8zNzCx2foURTtA3ckYQJQyNg/
lYIWF/pY+VhsU5+BYilaf7JdjChjRkg3FH+pWrY2Mf2iKLPwS+5PnSBVfhqZCGqF
B9pm4KV350JX2g11GSysCaZJBXqsEntYaow1mENOwTq66uJHucIbh0KcL5PX5KEG
pLhJK+ECgYEAzVtiwXd1PVW35F3qwtSAszFZTLKIuHrGeAG4o1DSbpm6df3q16Xf
PTugw6VuAxRE/sqFBfvG+H7WWjNZkHiSEmoZAkAGsXWNyKM/XxI05SrhwBDmw+mw
aQib9PfgKb/otn39qwPjnjKw1eXSFxhMPYL52Reorf/DHWHIKbkSTscCgYEAxwFb
EYtWSm9657/AjobMInSw503nHMcbWP5vEcsT2RSPkdOZAVyVRagyxReD/2RpQL7f
Qrdfsn21O8CZpYkIYqsuF9fP/NexuZgFj49u1i7g+Y6FLoaIOVtMmw+YJm8pm2rS
M7UMw9kOmfYN8JD44pIS9h0km6oTZHo8GbsAXfcCgYBAyLqv9AKtddRMnABKtIVh
goj8dDpDkJ/6Dfj0tLOeJqs3PAKRQ4fYpm4CKrc5C3T0uGkcySAtFr6CuD5iIFdc
rdHz7sTtyPsQt8dvM6wyO8P6NprGZXu8tvWUY3p5UUyV/cs/3zs4lh9Ja3ZKyOSM
Zzxw61DQi6Y/J7Dg0Lzg0wKBgQCiYnvSPBWElaT/mBti8aF++CMmCw5sEBhDrRIq
vcALYdipELWIQ+jWNyJ+aurdqiyslVOOmB0xg5wwDsARMFk0UiRBdmuUENlH7UGU
XGD/yq7vVBle1o4v500CNl5b9ldIJ4kwgirRYLuma/4B7/n2v2VTiIJHtyct1QRX
ppztDwKBgQCLNHvLVvOKNweAear/Uk93h+PHp+HfweTy4yG1Xpj3A2BZKy/ySnSU
GtkJZpq5CaEA/U8UWpDXGS8U1KFhDeHSBJcVzF8zwGMxhcArWFcgHmj7jWVBYH89
Mj7aDzM8w/ey8p0vi+0KbQNeQSIUbiLnQD1Jj3k1mEU/FEPxuoulFg==
-----END RSA PRIVATE KEY-----

`

var rsaPubPEM string = `
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn6MgLVWPxXqLfMRoCS2t
XcRiJn/q+0h+Y2cNw0U0lQ6dIL5WlFhr0C8YPHLDiGxe2AzMG0jj7QAvZnBKIQUA
60WRoQ4MhS0mb66nqSZvPPfX74FNCdy7e0inW9CexBFKhW/UTI0PjF4Dl/fFdo5h
cPTgeaPsiWWMoKVFdgDBfjYAnJvDBzqYJfIZ61LrqIxrvHxmQ6ZoiLBc6ku2o6eH
NYmwfMM82nQWrqPNZVCcCSQtD7+CFiP4uNlTXIP9W436sDx+EsHI1HwEPFZA7Eb8
shTV5s6Z4tfYYTs5873U2OF6DLCppOwSy2bvBzGamib9icZnXIkOv9v9Vf13lEhN
AQIDAQAB
-----END PUBLIC KEY-----
`
Output:

func (*Signer) Valid

func (s *Signer) Valid() bool

Valid validates a signer.

Jump to

Keyboard shortcuts

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