Documentation ¶
Overview ¶
Package jwthelper provides JWT(JSON Web Token) functions based on jwt-go.
Index ¶
- Variables
- func NewCookie(tokenString string, options ...CookieOption) *http.Cookie
- func ParseClaims(tokenString string) (map[string]interface{}, error)
- type Claim
- type CookieOption
- func CookieDomain(domain string) CookieOption
- func CookieExpires(expires time.Time) CookieOption
- func CookieHttpOnly(httpOnly bool) CookieOption
- func CookieMaxAge(maxAge int) CookieOption
- func CookieName(name string) CookieOption
- func CookiePath(path string) CookieOption
- func CookieSecure(secure bool) CookieOption
- type MultipleKeysParser
- type MultipleKeysSigner
- type Parser
- type ParserOption
- type Signer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
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") )
var ( ErrInvalidMultipleKeysSigner = fmt.Errorf("invalid multiple keys signer") ErrSignerNotFound = fmt.Errorf("signer not found by given kid(key id)") )
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") )
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 ¶
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()...
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 ¶
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.
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 ¶
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 ¶
NewSignerFromFile creates a signer with given "alg"(RFC7518) and signing key file.
func (*Signer) SignedString ¶
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: