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 }