diff --git a/internal/testutil/gen/gen.go b/internal/testutil/gen/gen.go new file mode 100644 index 0000000..a9f5925 --- /dev/null +++ b/internal/testutil/gen/gen.go @@ -0,0 +1,58 @@ +// Package gen allows generating of example tokens and claims. +// +// go run ./internal/testutil/gen +package main + +import ( + "encoding/json" + "fmt" + "os" + + tu "github.com/zitadel/oidc/v2/internal/testutil" + "github.com/zitadel/oidc/v2/pkg/oidc" +) + +var custom = map[string]any{ + "foo": "Hello, World!", + "bar": struct { + Count int `json:"count,omitempty"` + Tags []string `json:"tags,omitempty"` + }{ + Count: 22, + Tags: []string{"some", "tags"}, + }, +} + +func main() { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + + accessToken, atClaims := tu.NewAccessTokenCustom( + tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, + tu.ValidExpiration.AddDate(99, 0, 0), tu.ValidJWTID, + tu.ValidClientID, tu.ValidSkew, custom, + ) + atHash, err := oidc.ClaimHash(accessToken, tu.SignatureAlgorithm) + if err != nil { + panic(err) + } + + idToken, idClaims := tu.NewIDTokenCustom( + tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, + tu.ValidExpiration.AddDate(99, 0, 0), tu.ValidAuthTime, + tu.ValidNonce, tu.ValidACR, tu.ValidAMR, tu.ValidClientID, + tu.ValidSkew, atHash, custom, + ) + + fmt.Println("access token claims:") + if err := enc.Encode(atClaims); err != nil { + panic(err) + } + fmt.Printf("access token:\n%s\n", accessToken) + + fmt.Println("ID token claims:") + if err := enc.Encode(idClaims); err != nil { + panic(err) + } + fmt.Printf("ID token:\n%s\n", idToken) +} diff --git a/pkg/client/rp/verifier_test.go b/pkg/client/rp/verifier_test.go index b80ef22..7588c1f 100644 --- a/pkg/client/rp/verifier_test.go +++ b/pkg/client/rp/verifier_test.go @@ -62,7 +62,7 @@ func TestVerifyTokens(t *testing.T) { wantErr: true, }, { - name: "wronf access token", + name: "wrong access token", accessToken: accessToken, idTokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( diff --git a/pkg/client/rp/verifier_tokens_example_test.go b/pkg/client/rp/verifier_tokens_example_test.go new file mode 100644 index 0000000..a6974f8 --- /dev/null +++ b/pkg/client/rp/verifier_tokens_example_test.go @@ -0,0 +1,85 @@ +package rp_test + +import ( + "context" + "fmt" + + tu "github.com/zitadel/oidc/v2/internal/testutil" + "github.com/zitadel/oidc/v2/pkg/client/rp" + "github.com/zitadel/oidc/v2/pkg/oidc" +) + +// MyCustomClaims extends the TokenClaims base, +// so it implments the oidc.Claims interface. +// Instead of carying a map, we add needed fields +// to the struct for type safe access. +type MyCustomClaims struct { + oidc.TokenClaims + NotBefore oidc.Time `json:"nbf,omitempty"` + AccessTokenHash string `json:"at_hash,omitempty"` + Foo string `json:"foo,omitempty"` + Bar *Nested `json:"bar,omitempty"` +} + +// GetAccessTokenHash is required to implement +// the oidc.IDClaims interface. +func (c *MyCustomClaims) GetAccessTokenHash() string { + return c.AccessTokenHash +} + +// Nested struct types are also possible. +type Nested struct { + Count int `json:"count,omitempty"` + Tags []string `json:"tags,omitempty"` +} + +/* +idToken caries the following claims. foo and bar are custom claims + + { + "acr": "something", + "amr": [ + "foo", + "bar" + ], + "at_hash": "GKlH62ujLglHjxdM6ezzyQ", + "aud": [ + "unit", + "test", + "555666" + ], + "auth_time": 1678096954, + "azp": "555666", + "bar": { + "count": 22, + "tags": [ + "some", + "tags" + ] + }, + "client_id": "555666", + "exp": 4802234675, + "foo": "Hello, World!", + "iat": 1678097014, + "iss": "local.com", + "jti": "9876", + "nbf": 1678097014, + "nonce": "12345", + "sub": "tim@local.com" + } +*/ +const idToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOlsidW5pdCIsInRlc3QiXSwiYmFyIjoid29ybGQiLCJleHAiOjQ4MDIwMjU5MjAsImZvbyI6ImhlbGxvIiwiaWF0IjoxNjc3ODg4MjU5LCJpc3MiOiJsb2NhbC5jb20iLCJqdGkiOiI5ODc2IiwibmJmIjoxNjc3ODg4MjU5LCJzdWIiOiJ0aW1AbG9jYWwuY29tIn0.TbKRJnfyfn1PTC46VVXqqiKZl4gVmRPdQy8dxXvMtp1SAeMU4gSuu2qb-bNlVgFqQ5YqvveKH4mswcUf7DrqPx79roBEY1VZ6R0e10beZBg0UZ0XaBf9V9YJGTRQNEuETRjl6kMwVav4oyP8ZW74-AOrgSql7vxCX3FDRTRxt_7oeFRz2YzugFdHPOqQo4IHudQNMN9WD9b3QgoKDyj0BGxAQ9WpDE5N7WKIf6fXipSXJBQmf22QazXFZcUOGfKdhFYZ9eSlZQRDFJTguEtKwzk7wcxt6aJBsU-AEha2SucRXe3j7J56hAEsN5gC5i9edSdr8ebzrhnnLJ1t-PafhQ` +const accessToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOlsidW5pdCIsInRlc3QiXSwiYmFyIjp7ImNvdW50IjoyMiwidGFncyI6WyJzb21lIiwidGFncyJdfSwiZXhwIjo0ODAyMjM0Njc1LCJmb28iOiJIZWxsbywgV29ybGQhIiwiaWF0IjoxNjc4MDk3MDE0LCJpc3MiOiJsb2NhbC5jb20iLCJqdGkiOiI5ODc2IiwibmJmIjoxNjc4MDk3MDE0LCJzdWIiOiJ0aW1AbG9jYWwuY29tIn0.OUgk-B7OXjYlYFj-nogqSDJiQE19tPrbzqUHEAjcEiJkaWo6-IpGVfDiGKm-TxjXQsNScxpaY0Pg3XIh1xK6TgtfYtoLQm-5RYw_mXgb9xqZB2VgPs6nNEYFUDM513MOU0EBc0QMyqAEGzW-HiSPAb4ugCvkLtM1yo11Xyy6vksAdZNs_mJDT4X3vFXnr0jk0ugnAW6fTN3_voC0F_9HQUAkmd750OIxkAHxAMvEPQcpbLHenVvX_Q0QMrzClVrxehn5TVMfmkYYg7ocr876Bq9xQGPNHAcrwvVIJqdg5uMUA38L3HC2BEueG6furZGvc7-qDWAT1VR9liM5ieKpPg` + +func ExampleVerifyTokens_customClaims() { + v := rp.NewIDTokenVerifier("local.com", "555666", tu.KeySet{}) + + // VerifyAccessToken can be called with the *MyCustomClaims. + claims, err := rp.VerifyTokens[*MyCustomClaims](context.TODO(), accessToken, idToken, v) + if err != nil { + panic(err) + } + // Here we have typesafe access to the custom claims + fmt.Println(claims.Foo, claims.Bar.Count, claims.Bar.Tags) + // Output: Hello, World! 22 [some tags] +} diff --git a/pkg/op/verifier_access_token_example_test.go b/pkg/op/verifier_access_token_example_test.go index 2d986a0..0b0e0cb 100644 --- a/pkg/op/verifier_access_token_example_test.go +++ b/pkg/op/verifier_access_token_example_test.go @@ -21,7 +21,13 @@ type MyCustomClaims struct { Scopes []string `json:"scope,omitempty"` AccessTokenUseNumber int `json:"at_use_nbr,omitempty"` Foo string `json:"foo,omitempty"` - Bar string `json:"bar,omitempty"` + Bar *Nested `json:"bar,omitempty"` +} + +// Nested struct types are also possible. +type Nested struct { + Count int `json:"count,omitempty"` + Tags []string `json:"tags,omitempty"` } /* @@ -32,27 +38,34 @@ accessToken caries the following claims. foo and bar are custom claims "unit", "test" ], - "bar": "world", - "exp": 4802025920, - "foo": "hello", - "iat": 1677888259, + "bar": { + "count": 22, + "tags": [ + "some", + "tags" + ] + }, + "exp": 4802234675, + "foo": "Hello, World!", + "iat": 1678097014, "iss": "local.com", "jti": "9876", - "nbf": 1677888259, + "nbf": 1678097014, "sub": "tim@local.com" } */ -const accessToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOlsidW5pdCIsInRlc3QiXSwiYmFyIjoid29ybGQiLCJleHAiOjQ4MDIwMjU5MjAsImZvbyI6ImhlbGxvIiwiaWF0IjoxNjc3ODg4MjU5LCJpc3MiOiJsb2NhbC5jb20iLCJqdGkiOiI5ODc2IiwibmJmIjoxNjc3ODg4MjU5LCJzdWIiOiJ0aW1AbG9jYWwuY29tIn0.TbKRJnfyfn1PTC46VVXqqiKZl4gVmRPdQy8dxXvMtp1SAeMU4gSuu2qb-bNlVgFqQ5YqvveKH4mswcUf7DrqPx79roBEY1VZ6R0e10beZBg0UZ0XaBf9V9YJGTRQNEuETRjl6kMwVav4oyP8ZW74-AOrgSql7vxCX3FDRTRxt_7oeFRz2YzugFdHPOqQo4IHudQNMN9WD9b3QgoKDyj0BGxAQ9WpDE5N7WKIf6fXipSXJBQmf22QazXFZcUOGfKdhFYZ9eSlZQRDFJTguEtKwzk7wcxt6aJBsU-AEha2SucRXe3j7J56hAEsN5gC5i9edSdr8ebzrhnnLJ1t-PafhQ` +const accessToken = `eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOlsidW5pdCIsInRlc3QiXSwiYmFyIjp7ImNvdW50IjoyMiwidGFncyI6WyJzb21lIiwidGFncyJdfSwiZXhwIjo0ODAyMjM0Njc1LCJmb28iOiJIZWxsbywgV29ybGQhIiwiaWF0IjoxNjc4MDk3MDE0LCJpc3MiOiJsb2NhbC5jb20iLCJqdGkiOiI5ODc2IiwibmJmIjoxNjc4MDk3MDE0LCJzdWIiOiJ0aW1AbG9jYWwuY29tIn0.OUgk-B7OXjYlYFj-nogqSDJiQE19tPrbzqUHEAjcEiJkaWo6-IpGVfDiGKm-TxjXQsNScxpaY0Pg3XIh1xK6TgtfYtoLQm-5RYw_mXgb9xqZB2VgPs6nNEYFUDM513MOU0EBc0QMyqAEGzW-HiSPAb4ugCvkLtM1yo11Xyy6vksAdZNs_mJDT4X3vFXnr0jk0ugnAW6fTN3_voC0F_9HQUAkmd750OIxkAHxAMvEPQcpbLHenVvX_Q0QMrzClVrxehn5TVMfmkYYg7ocr876Bq9xQGPNHAcrwvVIJqdg5uMUA38L3HC2BEueG6furZGvc7-qDWAT1VR9liM5ieKpPg` func ExampleVerifyAccessToken_customClaims() { v := op.NewAccessTokenVerifier("local.com", tu.KeySet{}) - // Now VerifyAccessToken can be called with the *MyCustomClaims type to provide - // type safe access to all the Claims. + // VerifyAccessToken can be called with the *MyCustomClaims. claims, err := op.VerifyAccessToken[*MyCustomClaims](context.TODO(), accessToken, v) if err != nil { panic(err) } - fmt.Println(claims.Foo, claims.Bar) - // Output: hello world + + // Here we have typesafe access to the custom claims + fmt.Println(claims.Foo, claims.Bar.Count, claims.Bar.Tags) + // Output: Hello, World! 22 [some tags] }