From c466333a1be062693ad85fd22c0014c20e6dcf8b Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 13:18:31 -0700 Subject: [PATCH 1/4] expanded usefulness and correctness of examples --- example_test.go | 51 +++++++++++++---------------------- hmac_example_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 hmac_example_test.go diff --git a/example_test.go b/example_test.go index 4321f14..dff2855 100644 --- a/example_test.go +++ b/example_test.go @@ -2,41 +2,19 @@ package jwt_test import ( "fmt" - "time" - "github.com/dgrijalva/jwt-go" ) -func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { - token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { - return myLookupKey(token.Header["kid"]) - }) - - if err == nil && token.Valid { - fmt.Println("Your token is valid. I like your style.") - } else { - fmt.Println("This token is terrible! I cannot accept this.") - } -} - -func ExampleNew() { - // Create the token - token := jwt.New(jwt.SigningMethodRS256) - - // Set some claims - claims := token.Claims.(jwt.MapClaims) - claims["foo"] = "bar" - claims["exp"] = time.Unix(0, 0).Add(time.Hour * 1).Unix() - - fmt.Printf("<%T> foo:%v exp:%v\n", token.Claims, claims["foo"], claims["exp"]) - //Output: foo:bar exp:3600 -} - -func ExampleNewWithClaims() { +// Example (atypical) using the StandardClaims type by itself to parse a token. +// The StandardClaims type is designed to be embedded into your custom types +// to provide standard validation features. You can use it alone, but there's +// no way to retrieve other fields after parsing. +// See the CustomClaimsType example for intended usage. +func ExampleNewWithClaims_standardClaims() { mySigningKey := []byte("AllYourBase") // Create the Claims - claims := jwt.StandardClaims{ + claims := &jwt.StandardClaims{ ExpiresAt: 15000, Issuer: "test", } @@ -47,7 +25,9 @@ func ExampleNewWithClaims() { //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 } -func ExampleNewWithClaims_customType() { +// Example parsing a token using a custom claims type. The StandardClaim is embedded +// in the custom type to allow for easy encoding, parsing and validation of standard claims. +func ExampleNewWithClaims_customClaimsType() { mySigningKey := []byte("AllYourBase") type MyCustomClaims struct { @@ -70,9 +50,13 @@ func ExampleNewWithClaims_customType() { //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c } -func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) { - token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { - return myLookupKey(token.Header["kid"]) +// An example of parsing the error types using bitfield checks +func ExampleParse_errorChecking() { + // Token from another example. This token is expired + var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" + + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte("AllYourBase"), nil }) if token.Valid { @@ -90,4 +74,5 @@ func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (i fmt.Println("Couldn't handle this token:", err) } + // Output: Timing is everything } diff --git a/hmac_example_test.go b/hmac_example_test.go new file mode 100644 index 0000000..f8d2fc4 --- /dev/null +++ b/hmac_example_test.go @@ -0,0 +1,64 @@ +package jwt_test + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "io/ioutil" + "time" +) + +// For HMAC signing method, the key can be any []byte. It is recommended to generate +// a key using crypto/rand or something equivalent. You need the same key for signing +// and validating. +var hmacSampleSecret []byte + +func init() { + // Load sample key data + if keyData, e := ioutil.ReadFile("test/hmacTestKey"); e == nil { + hmacSampleSecret = keyData + } else { + panic(e) + } +} + +// Example creating, signing, and encoding a JWT token using the HMAC signing method +func ExampleNew_hmac() { + // Create a new token object, specifying signing method and the claims + // you would like it to contain. + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "foo": "bar", + "nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(), + }) + + // Sign and get the complete encoded token as a string using the secret + tokenString, err := token.SignedString(hmacSampleSecret) + + fmt.Println(tokenString, err) + // Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU +} + +// Example parsing and validating a token using the HMAC signing method +func ExampleParse_hmac() { + // sample token string taken from the New example + tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJuYmYiOjE0NDQ0Nzg0MDB9.u1riaD1rW97opCoAuRCTy4w58Br-Zk-bh7vLiRIsrpU" + + // Parse takes the token string and a function for looking up the key. The latter is especially + // useful if you use multiple keys for your application. The standard is to use 'kid' in the + // head of the token to identify which key to use, but the parsed token (head and claims) is provided + // to the callback, providing flexibility. + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return hmacSampleSecret, nil + }) + + if claims, ok := token.Claims.(*jwt.MapClaims); ok && token.Valid { + fmt.Println((*claims)["foo"], (*claims)["nbf"]) + } else { + fmt.Println(err) + } + + // Output: bar 1.4444784e+09 +} From 0c245a4f7ecfa253fcd9272dd5ad51d47be7c3b6 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 13:33:20 -0700 Subject: [PATCH 2/4] added example of parsing using custom type --- example_test.go | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/example_test.go b/example_test.go index dff2855..c1e399c 100644 --- a/example_test.go +++ b/example_test.go @@ -3,6 +3,7 @@ package jwt_test import ( "fmt" "github.com/dgrijalva/jwt-go" + "time" ) // Example (atypical) using the StandardClaims type by itself to parse a token. @@ -25,7 +26,7 @@ func ExampleNewWithClaims_standardClaims() { //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.QsODzZu3lUZMVdhbO76u3Jv02iYCvEHcYVUI1kOWEU0 } -// Example parsing a token using a custom claims type. The StandardClaim is embedded +// Example creating a token using a custom claims type. The StandardClaim is embedded // in the custom type to allow for easy encoding, parsing and validation of standard claims. func ExampleNewWithClaims_customClaimsType() { mySigningKey := []byte("AllYourBase") @@ -50,6 +51,41 @@ func ExampleNewWithClaims_customClaimsType() { //Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c } +// Example creating a token using a custom claims type. The StandardClaim is embedded +// in the custom type to allow for easy encoding, parsing and validation of standard claims. +func ExampleParseWithClaims_customClaimsType() { + tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" + + type MyCustomClaims struct { + Foo string `json:"foo"` + jwt.StandardClaims + } + + // sample token is expired. override time so it parses as valid + at(time.Unix(0, 0), func() { + token, err := jwt.ParseWithClaims(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte("AllYourBase"), nil + }, &MyCustomClaims{}) + + if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { + fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt) + } else { + fmt.Println(err) + } + }) + + // Output: bar 15000 +} + +// Override time value for tests. Restore default value after. +func at(t time.Time, f func()) { + jwt.TimeFunc = func() time.Time { + return t + } + f() + jwt.TimeFunc = time.Now +} + // An example of parsing the error types using bitfield checks func ExampleParse_errorChecking() { // Token from another example. This token is expired From 6f2e6588a2922d9225ebcfaee9b18a573153f7a8 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 13:34:11 -0700 Subject: [PATCH 3/4] replace inline examples with links to examples in the test suite. these examples are evaluated as part of the test pass, so they should remain correct --- README.md | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5966b88..e4829e5 100644 --- a/README.md +++ b/README.md @@ -17,37 +17,13 @@ The part in the middle is the interesting bit. It's called the Claims and conta This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. -## Parse and Verify +## Examples -Parsing and verifying tokens is pretty straight forward. You pass in the token and a function for looking up the key. This is done as a callback since you may need to parse the token to find out what signing method and key was used. +See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage: -```go - token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) - } - return myLookupKey(token.Header["kid"]), nil - }) - - if err == nil && token.Valid { - deliverGoodness("!") - } else { - deliverUtterRejection(":(") - } -``` - -## Create a token - -```go - // Create the token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "foo": "bar", - "exp": time.Now().Add(time.Hour * 72).Unix(), - }) - // Sign and get the complete encoded token as a string - tokenString, err := token.SignedString(mySigningKey) -``` +* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example_Parse_hmac) +* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example_New_hmac) +* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples) ## Extensions From 0145da90d469d9ec7f5a5cc72a4a2c28d276d093 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 13:39:14 -0700 Subject: [PATCH 4/4] added link to jwt.io --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e4829e5..7802b96 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A [go](http://www.golang.org) (or 'golang' for search engine friendliness) imple ## What the heck is a JWT? +JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens. + In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.