From b449ab2191ce0a7b7a13f37b5e09ecfb9924f985 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 2 Feb 2022 21:00:58 +0100 Subject: [PATCH] refactor: token -> jwt for accuracy --- pkg/{token => jwt}/access_token.go | 4 +- pkg/jwt/claims.go | 6 ++ pkg/{token => jwt}/id_token.go | 4 +- pkg/jwt/jwt.go | 48 +++++++++++++ pkg/jwt/jwt_ids.go | 6 ++ pkg/jwt/jwt_tokens.go | 50 +++++++++++++ pkg/jwt/type.go | 8 +++ pkg/loginstatus/loginstatus.go | 8 +-- pkg/loginstatus/loginstatus_test.go | 10 +-- pkg/router/handler_callback.go | 6 +- pkg/router/session.go | 6 +- pkg/router/session_fallback.go | 4 +- pkg/router/session_fallback_test.go | 26 +++---- pkg/router/session_id.go | 4 +- pkg/router/session_id_test.go | 18 ++--- pkg/session/memory_test.go | 14 ++-- pkg/session/redis_test.go | 14 ++-- pkg/session/session.go | 12 ++-- pkg/token/token.go | 108 ---------------------------- 19 files changed, 183 insertions(+), 173 deletions(-) rename pkg/{token => jwt}/access_token.go (92%) create mode 100644 pkg/jwt/claims.go rename pkg/{token => jwt}/id_token.go (96%) create mode 100644 pkg/jwt/jwt.go create mode 100644 pkg/jwt/jwt_ids.go create mode 100644 pkg/jwt/jwt_tokens.go create mode 100644 pkg/jwt/type.go delete mode 100644 pkg/token/token.go diff --git a/pkg/token/access_token.go b/pkg/jwt/access_token.go similarity index 92% rename from pkg/token/access_token.go rename to pkg/jwt/access_token.go index 2cefd84..d3a44db 100644 --- a/pkg/token/access_token.go +++ b/pkg/jwt/access_token.go @@ -1,4 +1,4 @@ -package token +package jwt import ( "github.com/lestrrat-go/jwx/jwk" @@ -28,7 +28,7 @@ func NewAccessToken(raw string, token jwt.Token) *AccessToken { } func ParseAccessToken(raw string, jwks jwk.Set) (*AccessToken, error) { - accessToken, err := ParseJwt(raw, jwks) + accessToken, err := Parse(raw, jwks) if err != nil { return nil, err } diff --git a/pkg/jwt/claims.go b/pkg/jwt/claims.go new file mode 100644 index 0000000..8641146 --- /dev/null +++ b/pkg/jwt/claims.go @@ -0,0 +1,6 @@ +package jwt + +const ( + JtiClaim = "jti" + SidClaim = "sid" +) diff --git a/pkg/token/id_token.go b/pkg/jwt/id_token.go similarity index 96% rename from pkg/token/id_token.go rename to pkg/jwt/id_token.go index 90ffd95..d45c516 100644 --- a/pkg/token/id_token.go +++ b/pkg/jwt/id_token.go @@ -1,4 +1,4 @@ -package token +package jwt import ( "time" @@ -58,7 +58,7 @@ func NewIDToken(raw string, token jwt.Token) *IDToken { } func ParseIDToken(raw string, jwks jwk.Set) (*IDToken, error) { - idToken, err := ParseJwt(raw, jwks) + idToken, err := Parse(raw, jwks) if err != nil { return nil, err } diff --git a/pkg/jwt/jwt.go b/pkg/jwt/jwt.go new file mode 100644 index 0000000..45c5161 --- /dev/null +++ b/pkg/jwt/jwt.go @@ -0,0 +1,48 @@ +package jwt + +import ( + "fmt" + + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jwt" +) + +func Parse(raw string, jwks jwk.Set) (jwt.Token, error) { + parseOpts := []jwt.ParseOption{ + jwt.WithKeySet(jwks), + jwt.InferAlgorithmFromKey(true), + } + token, err := jwt.ParseString(raw, parseOpts...) + if err != nil { + return nil, fmt.Errorf("parsing jwt: %w", err) + } + + return token, nil +} + +func GetStringClaim(token jwt.Token, claim string) (string, error) { + if token == nil { + return "", fmt.Errorf("token is nil") + } + + gotClaim, ok := token.Get(claim) + if !ok { + return "", fmt.Errorf("missing required '%s' claim in id_token", claim) + } + + claimString, ok := gotClaim.(string) + if !ok { + return "", fmt.Errorf("'%s' claim is not a string", claim) + } + + return claimString, nil +} + +func GetStringClaimOrEmpty(token jwt.Token, claim string) string { + str, err := GetStringClaim(token, claim) + if err != nil { + return "" + } + + return str +} diff --git a/pkg/jwt/jwt_ids.go b/pkg/jwt/jwt_ids.go new file mode 100644 index 0000000..728d01f --- /dev/null +++ b/pkg/jwt/jwt_ids.go @@ -0,0 +1,6 @@ +package jwt + +type IDs struct { + IDToken string `json:"id_token,omitempty"` + AccessToken string `json:"access_token,omitempty"` +} diff --git a/pkg/jwt/jwt_tokens.go b/pkg/jwt/jwt_tokens.go new file mode 100644 index 0000000..93c502d --- /dev/null +++ b/pkg/jwt/jwt_tokens.go @@ -0,0 +1,50 @@ +package jwt + +import ( + "fmt" + + "github.com/lestrrat-go/jwx/jwk" + "golang.org/x/oauth2" +) + +type Tokens struct { + IDToken *IDToken + AccessToken *AccessToken +} + +func (in *Tokens) JwtIDs() IDs { + return IDs{ + IDToken: in.IDToken.GetJtiClaim(), + AccessToken: in.AccessToken.GetJtiClaim(), + } +} + +func NewTokens(idToken *IDToken, accessToken *AccessToken) *Tokens { + return &Tokens{ + IDToken: idToken, + AccessToken: accessToken, + } +} + +func ParseOauth2Token(tokens *oauth2.Token, jwks jwk.Set) (*Tokens, error) { + idToken, ok := tokens.Extra("id_token").(string) + if !ok { + return nil, fmt.Errorf("missing id_token in token response") + } + + return ParseTokensFromStrings(idToken, tokens.AccessToken, jwks) +} + +func ParseTokensFromStrings(idToken, accessToken string, jwks jwk.Set) (*Tokens, error) { + parsedIdToken, err := ParseIDToken(idToken, jwks) + if err != nil { + return nil, fmt.Errorf("id_token: %w", err) + } + + parsedAccessToken, err := ParseAccessToken(accessToken, jwks) + if err != nil { + return nil, fmt.Errorf("access_token: %w", err) + } + + return NewTokens(parsedIdToken, parsedAccessToken), nil +} diff --git a/pkg/jwt/type.go b/pkg/jwt/type.go new file mode 100644 index 0000000..f313bfe --- /dev/null +++ b/pkg/jwt/type.go @@ -0,0 +1,8 @@ +package jwt + +type Type int + +const ( + TypeIDToken Type = iota + TypeAccessToken +) diff --git a/pkg/loginstatus/loginstatus.go b/pkg/loginstatus/loginstatus.go index 422d192..484767d 100644 --- a/pkg/loginstatus/loginstatus.go +++ b/pkg/loginstatus/loginstatus.go @@ -11,7 +11,7 @@ import ( "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/cookie" - "github.com/nais/wonderwall/pkg/token" + "github.com/nais/wonderwall/pkg/jwt" ) const ( @@ -19,7 +19,7 @@ const ( ) type Client interface { - ExchangeToken(ctx context.Context, accessToken *token.AccessToken) (*TokenResponse, error) + ExchangeToken(ctx context.Context, accessToken *jwt.AccessToken) (*TokenResponse, error) SetCookie(w http.ResponseWriter, token *TokenResponse, opts cookie.Options) HasCookie(r *http.Request) bool ClearCookie(w http.ResponseWriter, opts cookie.Options) @@ -47,7 +47,7 @@ type client struct { httpClient *http.Client } -func (c client) ExchangeToken(ctx context.Context, accessToken *token.AccessToken) (*TokenResponse, error) { +func (c client) ExchangeToken(ctx context.Context, accessToken *jwt.AccessToken) (*TokenResponse, error) { req, err := request(ctx, c.config.TokenURL, accessToken) if err != nil { return nil, fmt.Errorf("creating request %w", err) @@ -99,7 +99,7 @@ func (c client) cookieOptions(opts cookie.Options) cookie.Options { WithSameSite(SameSiteMode) } -func request(ctx context.Context, url string, token *token.AccessToken) (*http.Request, error) { +func request(ctx context.Context, url string, token *jwt.AccessToken) (*http.Request, error) { req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err diff --git a/pkg/loginstatus/loginstatus_test.go b/pkg/loginstatus/loginstatus_test.go index 60f5a24..d28ba2c 100644 --- a/pkg/loginstatus/loginstatus_test.go +++ b/pkg/loginstatus/loginstatus_test.go @@ -13,8 +13,8 @@ import ( "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/cookie" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/loginstatus" - "github.com/nais/wonderwall/pkg/token" ) func TestClient_ExchangeToken(t *testing.T) { @@ -25,18 +25,18 @@ func TestClient_ExchangeToken(t *testing.T) { client := loginstatus.NewClient(cfg, httpclient) for _, test := range []struct { - token *token.AccessToken + token *jwt.AccessToken err error }{ { - token: token.NewAccessToken("valid-token", nil), + token: jwt.NewAccessToken("valid-token", nil), }, { - token: token.NewAccessToken("invalid-token", nil), + token: jwt.NewAccessToken("invalid-token", nil), err: fmt.Errorf("client error: HTTP: %d: %s: %s", http.StatusUnauthorized, "access_denied", "No new and shiny token for you!"), }, { - token: token.NewAccessToken("internal-server-error", nil), + token: jwt.NewAccessToken("internal-server-error", nil), err: fmt.Errorf("server error: HTTP: %d: %s", http.StatusInternalServerError, "Oh no, it broke"), }, } { diff --git a/pkg/router/handler_callback.go b/pkg/router/handler_callback.go index 0e424f7..22f4a25 100644 --- a/pkg/router/handler_callback.go +++ b/pkg/router/handler_callback.go @@ -9,8 +9,8 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/oauth2" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/openid" - "github.com/nais/wonderwall/pkg/token" ) func (h *Handler) Callback(w http.ResponseWriter, r *http.Request) { @@ -41,7 +41,7 @@ func (h *Handler) Callback(w http.ResponseWriter, r *http.Request) { jwkSet := h.Provider.GetPublicJwkSet() - tokens, err := token.ParseTokens(rawTokens, *jwkSet) + tokens, err := jwt.ParseOauth2Token(rawTokens, *jwkSet) if err != nil { h.InternalError(w, r, fmt.Errorf("callback: parsing tokens: %w", err)) return @@ -94,7 +94,7 @@ func (h *Handler) codeExchangeForToken(ctx context.Context, loginCookie *openid. return tokens, nil } -func logSuccessfulLogin(tokens *token.Tokens, referer string) { +func logSuccessfulLogin(tokens *jwt.Tokens, referer string) { fields := log.Fields{ "redirect_to": referer, "jti": tokens.JwtIDs(), diff --git a/pkg/router/session.go b/pkg/router/session.go index d3dcc7b..10014f7 100644 --- a/pkg/router/session.go +++ b/pkg/router/session.go @@ -11,8 +11,8 @@ import ( "github.com/go-redis/redis/v8" log "github.com/sirupsen/logrus" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/session" - "github.com/nais/wonderwall/pkg/token" ) // localSessionID prefixes the given `sid` or `session_state` with the given client ID to prevent key collisions. @@ -64,7 +64,7 @@ func (h *Handler) getSession(ctx context.Context, sessionID string) (*session.Da return sessionData, nil } -func (h *Handler) getSessionLifetime(accessToken *token.AccessToken) time.Duration { +func (h *Handler) getSessionLifetime(accessToken *jwt.AccessToken) time.Duration { defaultSessionLifetime := h.Config.SessionMaxLifetime tokenDuration := accessToken.Token.Expiration().Sub(time.Now()) @@ -76,7 +76,7 @@ func (h *Handler) getSessionLifetime(accessToken *token.AccessToken) time.Durati return defaultSessionLifetime } -func (h *Handler) createSession(w http.ResponseWriter, r *http.Request, tokens *token.Tokens, params url.Values) error { +func (h *Handler) createSession(w http.ResponseWriter, r *http.Request, tokens *jwt.Tokens, params url.Values) error { externalSessionID, err := NewSessionID(h.Provider.GetOpenIDConfiguration(), tokens.IDToken, params) if err != nil { return fmt.Errorf("generating session ID: %w", err) diff --git a/pkg/router/session_fallback.go b/pkg/router/session_fallback.go index 3a8ce40..58a1534 100644 --- a/pkg/router/session_fallback.go +++ b/pkg/router/session_fallback.go @@ -6,8 +6,8 @@ import ( "net/http" "time" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/session" - "github.com/nais/wonderwall/pkg/token" ) func (h *Handler) SessionFallbackExternalIDCookieName() string { @@ -60,7 +60,7 @@ func (h *Handler) GetSessionFallback(r *http.Request) (*session.Data, error) { } jwkSet := h.Provider.GetPublicJwkSet() - tokens, err := token.ParseTokensFromStrings(idToken, accessToken, *jwkSet) + tokens, err := jwt.ParseTokensFromStrings(idToken, accessToken, *jwkSet) if err != nil { return nil, fmt.Errorf("parsing tokens: %w", err) } diff --git a/pkg/router/session_fallback_test.go b/pkg/router/session_fallback_test.go index 1d3e6a6..4835623 100644 --- a/pkg/router/session_fallback_test.go +++ b/pkg/router/session_fallback_test.go @@ -8,14 +8,14 @@ import ( "time" "github.com/lestrrat-go/jwx/jwa" - "github.com/lestrrat-go/jwx/jwt" + jwtlib "github.com/lestrrat-go/jwx/jwt" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/mock" "github.com/nais/wonderwall/pkg/router" "github.com/nais/wonderwall/pkg/session" - "github.com/nais/wonderwall/pkg/token" ) func TestHandler_GetSessionFallback(t *testing.T) { @@ -105,7 +105,7 @@ func TestHandler_DeleteSessionFallback(t *testing.T) { }) } -func makeRequestWithFallbackCookies(t *testing.T, h *router.Handler, tokens *token.Tokens) *http.Request { +func makeRequestWithFallbackCookies(t *testing.T, h *router.Handler, tokens *jwt.Tokens) *http.Request { writer := httptest.NewRecorder() expiresIn := time.Minute data := session.NewData("sid", tokens) @@ -150,7 +150,7 @@ func assertCookieExists(t *testing.T, h *router.Handler, cookieName, expectedVal assert.Equal(t, expectedValue, string(plainbytes)) } -func makeTokens(provider mock.TestProvider) *token.Tokens { +func makeTokens(provider mock.TestProvider) *jwt.Tokens { jwks := *provider.PrivateJwkSet() signer, ok := jwks.Get(0) @@ -158,30 +158,30 @@ func makeTokens(provider mock.TestProvider) *token.Tokens { log.Fatalf("getting signer") } - idToken := jwt.New() + idToken := jwtlib.New() idToken.Set("jti", "id-token-jti") - signedIdToken, err := jwt.Sign(idToken, jwa.RS256, signer) + signedIdToken, err := jwtlib.Sign(idToken, jwa.RS256, signer) if err != nil { log.Fatalf("signing id_token: %+v", err) } - parsedIdToken, err := jwt.Parse(signedIdToken) + parsedIdToken, err := jwtlib.Parse(signedIdToken) if err != nil { log.Fatalf("parsing signed id_token: %+v", err) } - accessToken := jwt.New() + accessToken := jwtlib.New() accessToken.Set("jti", "access-token-jti") - signedAccessToken, err := jwt.Sign(accessToken, jwa.RS256, signer) + signedAccessToken, err := jwtlib.Sign(accessToken, jwa.RS256, signer) if err != nil { log.Fatalf("signing access_token: %+v", err) } - parsedAccessToken, err := jwt.Parse(signedAccessToken) + parsedAccessToken, err := jwtlib.Parse(signedAccessToken) if err != nil { log.Fatalf("parsing signed access_token: %+v", err) } - return &token.Tokens{ - IDToken: token.NewIDToken(string(signedIdToken), parsedIdToken), - AccessToken: token.NewAccessToken(string(signedAccessToken), parsedAccessToken), + return &jwt.Tokens{ + IDToken: jwt.NewIDToken(string(signedIdToken), parsedIdToken), + AccessToken: jwt.NewAccessToken(string(signedAccessToken), parsedAccessToken), } } diff --git a/pkg/router/session_id.go b/pkg/router/session_id.go index cfd78d6..cb4c905 100644 --- a/pkg/router/session_id.go +++ b/pkg/router/session_id.go @@ -7,15 +7,15 @@ import ( "io" "net/url" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/openid" - "github.com/nais/wonderwall/pkg/token" ) const ( SessionStateParamKey = "session_state" ) -func NewSessionID(cfg *openid.Configuration, idToken *token.IDToken, params url.Values) (string, error) { +func NewSessionID(cfg *openid.Configuration, idToken *jwt.IDToken, params url.Values) (string, error) { // 1. check for 'sid' claim in id_token sessionID, err := idToken.GetSidClaim() if err == nil { diff --git a/pkg/router/session_id_test.go b/pkg/router/session_id_test.go index 2e9c265..e68ee7b 100644 --- a/pkg/router/session_id_test.go +++ b/pkg/router/session_id_test.go @@ -5,19 +5,19 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/jwt" + jwtlib "github.com/lestrrat-go/jwx/jwt" "github.com/stretchr/testify/assert" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/openid" "github.com/nais/wonderwall/pkg/router" - "github.com/nais/wonderwall/pkg/token" ) func TestSessionID(t *testing.T) { for _, test := range []struct { name string config *openid.Configuration - idToken *token.IDToken + idToken *jwt.IDToken params url.Values want string exactMatch bool @@ -136,8 +136,8 @@ func params(key, value string) url.Values { return values } -func newIDToken(extraClaims map[string]string) *token.IDToken { - idToken := jwt.New() +func newIDToken(extraClaims map[string]string) *jwt.IDToken { + idToken := jwtlib.New() idToken.Set("sub", "test") idToken.Set("iss", "test") idToken.Set("aud", "test") @@ -150,20 +150,20 @@ func newIDToken(extraClaims map[string]string) *token.IDToken { } } - serialized, err := jwt.NewSerializer().Serialize(idToken) + serialized, err := jwtlib.NewSerializer().Serialize(idToken) if err != nil { panic(err) } - return token.NewIDToken(string(serialized), idToken) + return jwt.NewIDToken(string(serialized), idToken) } -func idTokenWithSid(sid string) *token.IDToken { +func idTokenWithSid(sid string) *jwt.IDToken { return newIDToken(map[string]string{ "sid": sid, }) } -func idToken() *token.IDToken { +func idToken() *jwt.IDToken { return newIDToken(nil) } diff --git a/pkg/session/memory_test.go b/pkg/session/memory_test.go index 9a33d07..ac53290 100644 --- a/pkg/session/memory_test.go +++ b/pkg/session/memory_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - "github.com/lestrrat-go/jwx/jwt" + jwtlib "github.com/lestrrat-go/jwx/jwt" "github.com/nais/liberator/pkg/keygen" "github.com/stretchr/testify/assert" "github.com/nais/wonderwall/pkg/crypto" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/session" - "github.com/nais/wonderwall/pkg/token" ) func TestMemory(t *testing.T) { @@ -19,15 +19,15 @@ func TestMemory(t *testing.T) { assert.NoError(t, err) crypter := crypto.NewCrypter(key) - idToken := jwt.New() + idToken := jwtlib.New() idToken.Set("jti", "id-token-jti") - accessToken := jwt.New() + accessToken := jwtlib.New() accessToken.Set("jti", "access-token-jti") - tokens := &token.Tokens{ - IDToken: token.NewIDToken("id_token", idToken), - AccessToken: token.NewAccessToken("access_token", accessToken), + tokens := &jwt.Tokens{ + IDToken: jwt.NewIDToken("id_token", idToken), + AccessToken: jwt.NewAccessToken("access_token", accessToken), } data := session.NewData("myid", tokens) diff --git a/pkg/session/redis_test.go b/pkg/session/redis_test.go index ac721d1..e55a706 100644 --- a/pkg/session/redis_test.go +++ b/pkg/session/redis_test.go @@ -7,13 +7,13 @@ import ( "github.com/alicebob/miniredis/v2" "github.com/go-redis/redis/v8" - "github.com/lestrrat-go/jwx/jwt" + jwtlib "github.com/lestrrat-go/jwx/jwt" "github.com/nais/liberator/pkg/keygen" "github.com/stretchr/testify/assert" "github.com/nais/wonderwall/pkg/crypto" + "github.com/nais/wonderwall/pkg/jwt" "github.com/nais/wonderwall/pkg/session" - "github.com/nais/wonderwall/pkg/token" ) func TestRedis(t *testing.T) { @@ -21,15 +21,15 @@ func TestRedis(t *testing.T) { assert.NoError(t, err) crypter := crypto.NewCrypter(key) - idToken := jwt.New() + idToken := jwtlib.New() idToken.Set("jti", "id-token-jti") - accessToken := jwt.New() + accessToken := jwtlib.New() accessToken.Set("jti", "access-token-jti") - tokens := &token.Tokens{ - IDToken: token.NewIDToken("id_token", idToken), - AccessToken: token.NewAccessToken("access_token", accessToken), + tokens := &jwt.Tokens{ + IDToken: jwt.NewIDToken("id_token", idToken), + AccessToken: jwt.NewAccessToken("access_token", accessToken), } data := session.NewData("myid", tokens) diff --git a/pkg/session/session.go b/pkg/session/session.go index 23943c2..ddea52b 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -11,7 +11,7 @@ import ( "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/crypto" - "github.com/nais/wonderwall/pkg/token" + "github.com/nais/wonderwall/pkg/jwt" ) type Store interface { @@ -80,13 +80,13 @@ func (in *EncryptedData) Decrypt(crypter crypto.Crypter) (*Data, error) { } type Data struct { - ExternalSessionID string `json:"external_session_id"` - AccessToken string `json:"access_token"` - IDToken string `json:"id_token"` - JwtIDs token.JwtIDs `json:"jti"` + ExternalSessionID string `json:"external_session_id"` + AccessToken string `json:"access_token"` + IDToken string `json:"id_token"` + JwtIDs jwt.IDs `json:"jti"` } -func NewData(externalSessionID string, tokens *token.Tokens) *Data { +func NewData(externalSessionID string, tokens *jwt.Tokens) *Data { return &Data{ ExternalSessionID: externalSessionID, AccessToken: tokens.AccessToken.Raw, diff --git a/pkg/token/token.go b/pkg/token/token.go deleted file mode 100644 index 5f09aa0..0000000 --- a/pkg/token/token.go +++ /dev/null @@ -1,108 +0,0 @@ -package token - -import ( - "fmt" - - "github.com/lestrrat-go/jwx/jwk" - "github.com/lestrrat-go/jwx/jwt" - "golang.org/x/oauth2" -) - -type Type int - -const ( - TypeIDToken Type = iota - TypeAccessToken -) - -const ( - JtiClaim = "jti" - SidClaim = "sid" -) - -type Tokens struct { - IDToken *IDToken - AccessToken *AccessToken -} - -func (in *Tokens) JwtIDs() JwtIDs { - return JwtIDs{ - IDToken: in.IDToken.GetJtiClaim(), - AccessToken: in.AccessToken.GetJtiClaim(), - } -} - -func NewTokens(idToken *IDToken, accessToken *AccessToken) *Tokens { - return &Tokens{ - IDToken: idToken, - AccessToken: accessToken, - } -} - -type JwtIDs struct { - IDToken string `json:"id_token,omitempty"` - AccessToken string `json:"access_token,omitempty"` -} - -func ParseTokens(tokens *oauth2.Token, jwks jwk.Set) (*Tokens, error) { - idToken, ok := tokens.Extra("id_token").(string) - if !ok { - return nil, fmt.Errorf("missing id_token in token response") - } - - return ParseTokensFromStrings(idToken, tokens.AccessToken, jwks) -} - -func ParseTokensFromStrings(idToken, accessToken string, jwks jwk.Set) (*Tokens, error) { - parsedIdToken, err := ParseIDToken(idToken, jwks) - if err != nil { - return nil, fmt.Errorf("id_token: %w", err) - } - - parsedAccessToken, err := ParseAccessToken(accessToken, jwks) - if err != nil { - return nil, fmt.Errorf("access_token: %w", err) - } - - return NewTokens(parsedIdToken, parsedAccessToken), nil -} - -func ParseJwt(raw string, jwks jwk.Set) (jwt.Token, error) { - parseOpts := []jwt.ParseOption{ - jwt.WithKeySet(jwks), - jwt.InferAlgorithmFromKey(true), - } - token, err := jwt.ParseString(raw, parseOpts...) - if err != nil { - return nil, fmt.Errorf("parsing jwt: %w", err) - } - - return token, nil -} - -func GetStringClaim(token jwt.Token, claim string) (string, error) { - if token == nil { - return "", fmt.Errorf("token is nil") - } - - gotClaim, ok := token.Get(claim) - if !ok { - return "", fmt.Errorf("missing required '%s' claim in id_token", claim) - } - - claimString, ok := gotClaim.(string) - if !ok { - return "", fmt.Errorf("'%s' claim is not a string", claim) - } - - return claimString, nil -} - -func GetStringClaimOrEmpty(token jwt.Token, claim string) string { - str, err := GetStringClaim(token, claim) - if err != nil { - return "" - } - - return str -}