github.com/clerkinc/clerk-sdk-go@v1.49.1/clerk/tokens_test.go (about)

     1  package clerk
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/rand"
     6  	"crypto/rsa"
     7  	"crypto/x509"
     8  	"encoding/json"
     9  	"encoding/pem"
    10  	"net/http"
    11  	"reflect"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/go-jose/go-jose/v3"
    16  	"github.com/go-jose/go-jose/v3/jwt"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  var (
    21  	dummyTokenClaims = map[string]interface{}{
    22  		"iss":     "issuer",
    23  		"sub":     "subject",
    24  		"aud":     []string{"clerk"},
    25  		"name":    "name",
    26  		"picture": "picture",
    27  	}
    28  
    29  	dummyTokenClaimsExpected = TokenClaims{
    30  		Claims: jwt.Claims{
    31  			Issuer:   "issuer",
    32  			Subject:  "subject",
    33  			Audience: jwt.Audience{"clerk"},
    34  			Expiry:   nil,
    35  			IssuedAt: nil,
    36  		},
    37  		Extra: map[string]interface{}{
    38  			"name":    "name",
    39  			"picture": "picture",
    40  		},
    41  	}
    42  
    43  	dummySessionClaims = SessionClaims{
    44  		Claims: jwt.Claims{
    45  			Issuer:   "https://clerk.issuer",
    46  			Subject:  "subject",
    47  			Audience: nil,
    48  			Expiry:   nil,
    49  			IssuedAt: nil,
    50  		},
    51  		SessionID:                     "session_id",
    52  		AuthorizedParty:               "authorized_party",
    53  		ActiveOrganizationID:          "org_id",
    54  		ActiveOrganizationSlug:        "org_slug",
    55  		ActiveOrganizationRole:        "org_role",
    56  		ActiveOrganizationPermissions: []string{"org:billing:manage", "org:report:view"},
    57  	}
    58  )
    59  
    60  func TestClient_DecodeToken_EmptyToken(t *testing.T) {
    61  	c, _ := NewClient("token")
    62  
    63  	_, err := c.DecodeToken("")
    64  	if err == nil {
    65  		t.Errorf("Expected error to be returned")
    66  	}
    67  }
    68  
    69  func TestClient_DecodeToken_Success(t *testing.T) {
    70  	c, _ := NewClient("token")
    71  	token, _ := testGenerateTokenJWT(t, dummyTokenClaims, "kid")
    72  
    73  	got, _ := c.DecodeToken(token)
    74  
    75  	if !reflect.DeepEqual(got, &dummyTokenClaimsExpected) {
    76  		t.Errorf("Expected %+v, but got %+v", &dummyTokenClaimsExpected, got)
    77  	}
    78  }
    79  
    80  func TestClient_VerifyToken_EmptyToken(t *testing.T) {
    81  	c, _ := NewClient("token")
    82  
    83  	_, err := c.VerifyToken("")
    84  	if err == nil {
    85  		t.Errorf("Expected error to be returned")
    86  	}
    87  }
    88  
    89  func TestClient_VerifyToken_MissingKID(t *testing.T) {
    90  	c, _ := NewClient("token")
    91  	token, _ := testGenerateTokenJWT(t, dummySessionClaims, "")
    92  
    93  	_, err := c.VerifyToken(token)
    94  	if err == nil {
    95  		t.Errorf("Expected error to be returned")
    96  	}
    97  }
    98  
    99  func TestClient_VerifyToken_MismatchKID(t *testing.T) {
   100  	c, _ := NewClient("token")
   101  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   102  
   103  	client := c.(*client)
   104  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "invalid-kid"))
   105  
   106  	_, err := c.VerifyToken(token)
   107  	if err == nil {
   108  		t.Errorf("Expected error to be returned")
   109  	}
   110  }
   111  
   112  func TestClient_VerifyToken_MismatchAlgorithm(t *testing.T) {
   113  	c, _ := NewClient("token")
   114  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   115  
   116  	client := c.(*client)
   117  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS512, "kid"))
   118  
   119  	_, err := c.VerifyToken(token)
   120  	if err == nil {
   121  		t.Errorf("Expected error to be returned")
   122  	}
   123  }
   124  
   125  func TestClient_VerifyToken_InvalidKey(t *testing.T) {
   126  	c, _ := NewClient("token")
   127  	token, _ := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   128  	privKey, _ := rsa.GenerateKey(rand.Reader, 2048)
   129  
   130  	client := c.(*client)
   131  	client.jwksCache.set(testBuildJWKS(t, privKey.Public(), jose.RS256, "kid"))
   132  
   133  	_, err := c.VerifyToken(token)
   134  	if err == nil {
   135  		t.Errorf("Expected error to be returned")
   136  	}
   137  }
   138  
   139  func TestClient_VerifyToken_InvalidIssuer(t *testing.T) {
   140  	c, _ := NewClient("token")
   141  
   142  	claims := dummySessionClaims
   143  	claims.Issuer = "issuer"
   144  
   145  	token, pubKey := testGenerateTokenJWT(t, claims, "kid")
   146  
   147  	client := c.(*client)
   148  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   149  
   150  	_, err := c.VerifyToken(token)
   151  	if err == nil {
   152  		t.Errorf("Expected error to be returned")
   153  	}
   154  }
   155  
   156  func TestClient_VerifyToken_IssuerSatelliteDomain(t *testing.T) {
   157  	c, _ := NewClient("token")
   158  
   159  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   160  
   161  	client := c.(*client)
   162  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   163  
   164  	got, _ := c.VerifyToken(token, WithSatelliteDomain(true))
   165  	if !reflect.DeepEqual(got, &dummySessionClaims) {
   166  		t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got)
   167  	}
   168  }
   169  
   170  func TestClient_VerifyToken_InvalidIssuerProxyURL(t *testing.T) {
   171  	c, _ := NewClient("token")
   172  
   173  	claims := dummySessionClaims
   174  	claims.Issuer = "invalid"
   175  
   176  	token, pubKey := testGenerateTokenJWT(t, claims, "kid")
   177  
   178  	client := c.(*client)
   179  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   180  
   181  	_, err := c.VerifyToken(token, WithProxyURL("issuer"))
   182  	if err == nil {
   183  		t.Errorf("Expected error to be returned")
   184  	}
   185  }
   186  
   187  func TestClient_VerifyToken_ValidIssuerProxyURL(t *testing.T) {
   188  	c, _ := NewClient("token")
   189  
   190  	claims := dummySessionClaims
   191  	claims.Issuer = "issuer"
   192  
   193  	token, pubKey := testGenerateTokenJWT(t, claims, "kid")
   194  
   195  	client := c.(*client)
   196  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   197  
   198  	got, _ := c.VerifyToken(token, WithProxyURL("issuer"))
   199  	if !reflect.DeepEqual(got, &claims) {
   200  		t.Errorf("Expected %+v, but got %+v", claims, got)
   201  	}
   202  }
   203  
   204  func TestClient_VerifyToken_ExpiredToken(t *testing.T) {
   205  	c, _ := NewClient("token")
   206  
   207  	expiredClaims := dummySessionClaims
   208  	expiredClaims.Expiry = jwt.NewNumericDate(time.Now().Add(time.Second * -1))
   209  	token, pubKey := testGenerateTokenJWT(t, expiredClaims, "kid")
   210  
   211  	client := c.(*client)
   212  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   213  
   214  	_, err := c.VerifyToken(token)
   215  	if err == nil {
   216  		t.Errorf("Expected error to be returned")
   217  	}
   218  }
   219  
   220  func TestClient_VerifyToken_InvalidAuthorizedParty(t *testing.T) {
   221  	c, _ := NewClient("token")
   222  
   223  	claims := dummySessionClaims
   224  	claims.AuthorizedParty = "fake-party"
   225  
   226  	token, pubKey := testGenerateTokenJWT(t, claims, "kid")
   227  
   228  	client := c.(*client)
   229  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   230  
   231  	_, err := c.VerifyToken(token, WithAuthorizedParty("authorized_party"))
   232  	if err == nil {
   233  		t.Errorf("Expected error to be returned")
   234  	}
   235  }
   236  
   237  func TestClient_VerifyToken_Success(t *testing.T) {
   238  	c, _ := NewClient("token")
   239  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   240  
   241  	client := c.(*client)
   242  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   243  
   244  	got, _ := c.VerifyToken(token)
   245  	if !reflect.DeepEqual(got, &dummySessionClaims) {
   246  		t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got)
   247  	}
   248  }
   249  
   250  func TestClient_VerifyToken_Success_NewIssuerFormat(t *testing.T) {
   251  	c, _ := NewClient("token")
   252  
   253  	claims := dummySessionClaims
   254  	claims.Issuer = "https://foo-bar-13.clerk.accounts.dev"
   255  
   256  	token, pubKey := testGenerateTokenJWT(t, claims, "kid")
   257  
   258  	client := c.(*client)
   259  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   260  
   261  	got, err := c.VerifyToken(token)
   262  	if err != nil {
   263  		t.Fatalf("Expected no error but got %v", err)
   264  	}
   265  
   266  	if !reflect.DeepEqual(got, &claims) {
   267  		t.Errorf("Expected %+v, but got %+v", claims, got)
   268  	}
   269  }
   270  
   271  func TestClient_VerifyToken_Success_ExpiredCache(t *testing.T) {
   272  	c, mux, _, teardown := setup("token")
   273  	defer teardown()
   274  
   275  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   276  
   277  	mux.HandleFunc("/jwks", func(w http.ResponseWriter, req *http.Request) {
   278  		testHttpMethod(t, req, "GET")
   279  		testHeader(t, req, "Authorization", "Bearer token")
   280  		_ = json.NewEncoder(w).Encode(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   281  	})
   282  
   283  	client := c.(*client)
   284  	client.jwksCache.expiresAt = time.Now().Add(time.Second * -5)
   285  
   286  	got, _ := c.VerifyToken(token)
   287  	if !reflect.DeepEqual(got, &dummySessionClaims) {
   288  		t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got)
   289  	}
   290  }
   291  
   292  func TestClient_VerifyToken_Success_AuthorizedParty(t *testing.T) {
   293  	c, mux, _, teardown := setup("token")
   294  	defer teardown()
   295  
   296  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   297  
   298  	mux.HandleFunc("/jwks", func(w http.ResponseWriter, req *http.Request) {
   299  		testHttpMethod(t, req, "GET")
   300  		testHeader(t, req, "Authorization", "Bearer token")
   301  		_ = json.NewEncoder(w).Encode(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   302  	})
   303  
   304  	client := c.(*client)
   305  	client.jwksCache.expiresAt = time.Now().Add(time.Second * -5)
   306  
   307  	got, _ := c.VerifyToken(token, WithAuthorizedParty("authorized_party"))
   308  	if !reflect.DeepEqual(got, &dummySessionClaims) {
   309  		t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got)
   310  	}
   311  }
   312  
   313  func TestClient_VerifyToken_Success_WithLeeway(t *testing.T) {
   314  	c, _ := NewClient("token")
   315  
   316  	expiredClaims := dummySessionClaims
   317  	expiredClaims.Expiry = jwt.NewNumericDate(time.Now().Add(time.Second * -1))
   318  	token, pubKey := testGenerateTokenJWT(t, expiredClaims, "kid")
   319  
   320  	client := c.(*client)
   321  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   322  
   323  	_, err := c.VerifyToken(token, WithLeeway(3*time.Second))
   324  	if err != nil {
   325  		t.Errorf("Expected no error to be returned, but got: %+v", err)
   326  	}
   327  }
   328  
   329  func TestClient_VerifyToken_Success_WithJWTVerificationKey(t *testing.T) {
   330  	c, _ := NewClient("token")
   331  	token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   332  	verificationKey := testRSAPublicKeyToPEM(t, pubKey)
   333  
   334  	_, err := c.VerifyToken(token, WithJWTVerificationKey(verificationKey))
   335  	if err != nil {
   336  		t.Errorf("Expected no error, but got: %+v", err)
   337  	}
   338  }
   339  
   340  func TestClient_VerifyToken_Success_WithCustomClaims(t *testing.T) {
   341  	c, _ := NewClient("token")
   342  
   343  	expectedClaims := map[string]interface{}{
   344  		"iss":       "https://clerk.issuer",
   345  		"sub":       "subject",
   346  		"sid":       "session_id",
   347  		"azp":       "authorized_party",
   348  		"role":      "tester",
   349  		"interests": []string{"tennis", "football"},
   350  	}
   351  
   352  	token, pubKey := testGenerateTokenJWT(t, expectedClaims, "kid")
   353  
   354  	client := c.(*client)
   355  	client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid"))
   356  
   357  	customClaims := struct {
   358  		Issuer    string   `json:"iss"`
   359  		UserID    string   `json:"sub"`
   360  		Role      string   `json:"role"`
   361  		Interests []string `json:"interests"`
   362  	}{}
   363  
   364  	_, _ = c.VerifyToken(token, WithCustomClaims(&customClaims))
   365  	assert.Equal(t, expectedClaims["iss"], customClaims.Issuer)
   366  	assert.Equal(t, expectedClaims["sub"], customClaims.UserID)
   367  	assert.Equal(t, expectedClaims["role"], customClaims.Role)
   368  	assert.Equal(t, expectedClaims["interests"], customClaims.Interests)
   369  }
   370  
   371  func TestClient_VerifyToken_Error_WithJWTVerificationKey(t *testing.T) {
   372  	c, _ := NewClient("token")
   373  	token, _ := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   374  
   375  	// Generate new public key not matching the one from the token
   376  	_, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid")
   377  	verificationKey := testRSAPublicKeyToPEM(t, pubKey)
   378  
   379  	_, err := c.VerifyToken(token, WithJWTVerificationKey(verificationKey))
   380  	if err == nil {
   381  		t.Errorf("Expected error to be returned")
   382  	}
   383  }
   384  
   385  func testGenerateTokenJWT(t *testing.T, claims interface{}, kid string) (string, crypto.PublicKey) {
   386  	t.Helper()
   387  
   388  	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
   389  	if err != nil {
   390  		t.Error(err)
   391  	}
   392  
   393  	signerOpts := &jose.SignerOptions{}
   394  	signerOpts.WithType("JWT")
   395  	if kid != "" {
   396  		signerOpts.WithHeader("kid", kid)
   397  	}
   398  
   399  	signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privKey}, signerOpts)
   400  	if err != nil {
   401  		t.Error(err)
   402  	}
   403  
   404  	builder := jwt.Signed(signer)
   405  	builder = builder.Claims(claims)
   406  
   407  	token, err := builder.CompactSerialize()
   408  	if err != nil {
   409  		t.Error(err)
   410  	}
   411  
   412  	return token, privKey.Public()
   413  }
   414  
   415  func testBuildJWKS(t *testing.T, pubKey crypto.PublicKey, alg jose.SignatureAlgorithm, kid string) *JWKS {
   416  	t.Helper()
   417  
   418  	return &JWKS{Keys: []jose.JSONWebKey{
   419  		{
   420  			Key:       pubKey,
   421  			KeyID:     kid,
   422  			Algorithm: string(alg),
   423  			Use:       "sig",
   424  		},
   425  	}}
   426  }
   427  
   428  func testRSAPublicKeyToPEM(t *testing.T, input interface{}) string {
   429  	t.Helper()
   430  
   431  	rsaPubKey, ok := input.(*rsa.PublicKey)
   432  	if !ok {
   433  		t.Error("provided input is not an RSA public key")
   434  	}
   435  
   436  	pubKeyBytes, err := x509.MarshalPKIXPublicKey(rsaPubKey)
   437  	if err != nil {
   438  		t.Error("failed to marshal public key")
   439  	}
   440  
   441  	pemBytes := pem.EncodeToMemory(&pem.Block{
   442  		Type:  "PUBLIC KEY",
   443  		Bytes: pubKeyBytes,
   444  	})
   445  
   446  	return string(pemBytes)
   447  }