Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace HMAC with ED25519 in examples #310

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 71 additions & 19 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package jwt_test

import (
"crypto/ed25519"
"encoding/hex"
"errors"
"fmt"
"time"
Expand All @@ -14,24 +16,45 @@ import (
// no way to retrieve other fields after parsing.
// See the CustomClaimsType example for intended usage.
func ExampleNewWithClaims_registeredClaims() {
mySigningKey := []byte("AllYourBase")
publicKey, privateKey, err := ed25519.GenerateKey(nil)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we should load one of the keys from the test repository instead of generating one. This would probably be a bit more "realistic". Hopefully people will understand to load their own key instead of using the one in the test folder then.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that other tests load the key from the filesystem, so here I decided to show that they can also be generated if needed. At least it's secure and useful for quick prototypying.

if err != nil {
panic(err)
}

// Create the Claims
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Unix(1516239022, 0)),
ExpiresAt: jwt.NewNumericDate(time.Unix(2516239022, 0)),
Issuer: "test",
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.0XN_1Tpp9FszFOonIBpwha0c_SfnNI22DhTnjMshPg8 <nil>
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
ss, err := token.SignedString(privateKey)
if err != nil {
panic(err)
}

// Validate the token
tk, err := jwt.ParseWithClaims(ss, claims, func(t *jwt.Token) (interface{}, error) {
return publicKey, nil
})

if err != nil {
panic(err)
}

issuer, err := tk.Claims.GetIssuer()
fmt.Printf("%v %v", issuer, err)

//Output: test <nil>
}

// Example creating a token using a custom claims type. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of registered claims.
func ExampleNewWithClaims_customClaimsType() {
mySigningKey := []byte("AllYourBase")
_, privateKey, err := ed25519.GenerateKey(nil)
if err != nil {
panic(err)
}

type MyCustomClaims struct {
Foo string `json:"foo"`
Expand Down Expand Up @@ -65,26 +88,34 @@ func ExampleNewWithClaims_customClaimsType() {
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
ss, err := token.SignedString(privateKey)

fmt.Println(len(ss), err)

// Output: foo: bar
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
// 182 <nil>
}

// Example creating a token using a custom claims type. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_customClaimsType() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
tokenString := "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoyNTE2MjM5MDIyfQ.JutXls8z2IUxAtUgCV2Ec7WRVKrTYX5gCByB0mGLJw0qC9xah3YwH9E82U3QZAPQOOXAalhEFP92KYEWAyITDw"

// Corresponding private key is a426d77a4edabfdef2830223c9e94e68c5ba1006d1d7ba2a8277ada9b3f93d5c9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56
publicKeyBytes, err := hex.DecodeString("9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56")
if err != nil {
panic(err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)

type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
return publicKey, nil
})

if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
Expand All @@ -99,15 +130,22 @@ func ExampleParseWithClaims_customClaimsType() {
// Example creating a token using a custom claims type and validation options. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_validationOptions() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
tokenString := "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoyNTE2MjM5MDIyfQ.JutXls8z2IUxAtUgCV2Ec7WRVKrTYX5gCByB0mGLJw0qC9xah3YwH9E82U3QZAPQOOXAalhEFP92KYEWAyITDw"

// Corresponding private key is a426d77a4edabfdef2830223c9e94e68c5ba1006d1d7ba2a8277ada9b3f93d5c9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56
publicKeyBytes, err := hex.DecodeString("9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56")
if err != nil {
panic(err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)

type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
return publicKey, nil
}, jwt.WithLeeway(5*time.Second))

if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
Expand Down Expand Up @@ -139,10 +177,17 @@ func (m MyCustomClaims) Validate() error {
// encoding, parsing and validation of standard claims and the function
// CustomValidation is implemented.
func ExampleParseWithClaims_customValidation() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"
tokenString := "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoyNTE2MjM5MDIyfQ.JutXls8z2IUxAtUgCV2Ec7WRVKrTYX5gCByB0mGLJw0qC9xah3YwH9E82U3QZAPQOOXAalhEFP92KYEWAyITDw"

// Corresponding private key is a426d77a4edabfdef2830223c9e94e68c5ba1006d1d7ba2a8277ada9b3f93d5c9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56
publicKeyBytes, err := hex.DecodeString("9939cf856f57ee490076a5cc0104b7ae7d458be275cd1cc6fb91b509413e7f56")
if err != nil {
panic(err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)

token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
return publicKey, nil
}, jwt.WithLeeway(5*time.Second))

if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
Expand All @@ -157,10 +202,17 @@ func ExampleParseWithClaims_customValidation() {
// An example of parsing the error types using errors.Is.
func ExampleParse_errorChecking() {
// Token from another example. This token is expired
var tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
var tokenString = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.XAxYEk_6fWzMe256fXeT-eDR1vo_t4gqkq3eD4lqKHm8VrvhnOBtIrfXAJvMY6S1c5Lb1CIhPAaCe366xsYJDg"

// Corresponding private key is 378446363ffa1eb526a61f4b250bd24bd60002d5d20e22fa9b20c786e7f5e2ea8fff42935411c4c9bd3772c4a96a710bf6f2ba5508a71fc6155bcd73eb952837
publicKeyBytes, err := hex.DecodeString("8fff42935411c4c9bd3772c4a96a710bf6f2ba5508a71fc6155bcd73eb952837")
if err != nil {
panic(err)
}
publicKey := ed25519.PublicKey(publicKeyBytes)

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
return publicKey, nil
})

if token.Valid {
Expand Down
127 changes: 127 additions & 0 deletions hmac_example_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jwt_test

import (
"errors"
"fmt"
"os"
"time"
Expand Down Expand Up @@ -65,3 +66,129 @@ func ExampleParse_hmac() {

// Output: bar 1.4444784e+09
}

// Example creating a token using a custom claims type. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of registered claims.
func ExampleNewWithClaims_customClaimsTypeHmac() {
mySigningKey := []byte("AllYourBase")

type SomeCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

// Create claims with multiple fields populated
claims := SomeCustomClaims{
"bar",
jwt.RegisteredClaims{
// A usual scenario is to set the expiration time relative to the current time
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "test",
Subject: "somebody",
ID: "1",
Audience: []string{"somebody_else"},
},
}

fmt.Printf("foo: %v\n", claims.Foo)

// Create claims while leaving out some of the optional fields
claims = SomeCustomClaims{
"bar",
jwt.RegisteredClaims{
// Also fixed dates can be used for the NumericDate
ExpiresAt: jwt.NewNumericDate(time.Unix(1516239022, 0)),
Issuer: "test",
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)

//Output: foo: bar
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiZXhwIjoxNTE2MjM5MDIyfQ.xVuY2FZ_MRXMIEgVQ7J-TFtaucVFRXUzHm9LmV41goM <nil>
}

// Example creating a token using a custom claims type. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_customClaimsTypeHmac() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"

type SomeCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

token, err := jwt.ParseWithClaims(tokenString, &SomeCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})

if claims, ok := token.Claims.(*SomeCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer)
} else {
fmt.Println(err)
}

// Output: bar test
}

// Example creating a token using a custom claims type and validation options. The RegisteredClaims is embedded
// in the custom type to allow for easy encoding, parsing and validation of standard claims.
func ExampleParseWithClaims_validationOptionsHmac() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"

type SomeCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

token, err := jwt.ParseWithClaims(tokenString, &SomeCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
}, jwt.WithLeeway(5*time.Second))

if claims, ok := token.Claims.(*SomeCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer)
} else {
fmt.Println(err)
}

// Output: bar test
}

type SomeCustomClaims struct {
Foo string `json:"foo"`
jwt.RegisteredClaims
}

// Validate can be used to execute additional application-specific claims
// validation.
func (m SomeCustomClaims) Validate() error {
if m.Foo != "bar" {
return errors.New("must be foobar")
}

return nil
}

// Example creating a token using a custom claims type and validation options.
// The RegisteredClaims is embedded in the custom type to allow for easy
// encoding, parsing and validation of standard claims and the function
// CustomValidation is implemented.
func ExampleParseWithClaims_customValidationHmac() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpc3MiOiJ0ZXN0IiwiYXVkIjoic2luZ2xlIn0.QAWg1vGvnqRuCFTMcPkjZljXHh8U3L_qUjszOtQbeaA"

token, err := jwt.ParseWithClaims(tokenString, &SomeCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
}, jwt.WithLeeway(5*time.Second))

if claims, ok := token.Claims.(*SomeCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.RegisteredClaims.Issuer)
} else {
fmt.Println(err)
}

// Output: bar test
}