refactor: token -> jwt for accuracy

This commit is contained in:
Trong Huu Nguyen
2022-02-03 11:41:43 +01:00
parent eeccebc5dd
commit b449ab2191
19 changed files with 183 additions and 173 deletions
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
package jwt
const (
JtiClaim = "jti"
SidClaim = "sid"
)
@@ -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
}
+48
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
package jwt
type IDs struct {
IDToken string `json:"id_token,omitempty"`
AccessToken string `json:"access_token,omitempty"`
}
+50
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
package jwt
type Type int
const (
TypeIDToken Type = iota
TypeAccessToken
)
+4 -4
View File
@@ -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
+5 -5
View File
@@ -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"),
},
} {
+3 -3
View File
@@ -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(),
+3 -3
View File
@@ -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)
+2 -2
View File
@@ -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)
}
+13 -13
View File
@@ -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),
}
}
+2 -2
View File
@@ -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 {
+9 -9
View File
@@ -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)
}
+7 -7
View File
@@ -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)
+7 -7
View File
@@ -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)
+6 -6
View File
@@ -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,
-108
View File
@@ -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
}