# How OIDC ID-Token JWKS Verification Integrates with Login and Tenant Provisioning in WeKnora

> Learn how WeKnora uses OIDC ID-token JWKS verification to securely integrate login and tenant provisioning. Ensure only validated identities get organizational access.

- Repository: [Tencent/WeKnora](https://github.com/tencent/WeKnora)
- Tags: how-to-guide
- Published: 2026-09-12

---

**WeKnora cryptographically verifies OIDC ID tokens against provider JWKS endpoints during the authentication callback before automatically provisioning or linking tenant accounts, ensuring only validated identities receive organizational access.**

WeKnora, Tencent's open-source cloud-native platform, implements a robust OpenID Connect (OIDC) authentication flow that tightly couples JSON Web Key Set (JWKS) verification with tenant lifecycle management. This integration ensures that cryptographic validation of identity tokens occurs immediately before tenant provisioning logic executes, creating a secure boundary between unverified authentication attempts and resource allocation.

## The Complete OIDC Authentication Flow

The authentication process spans multiple layers of the WeKnora architecture, from configuration loading through final tenant association.

### Discovery and Configuration Loading

The flow begins when the client retrieves OIDC provider metadata via `GET /api/v1/auth/oidc/config`. This endpoint returns the discovery document containing the **JWKS URL** essential for subsequent token verification. The configuration structure is defined in [[`internal/config/config.go`](https://github.com/Tencent/WeKnora/blob/main/internal/config/config.go)](https://github.com/Tencent/WeKnora/blob/main/internal/config/config.go#L26) within the `OIDCAuthConfig` struct, with validation logic residing in the same file (approximately lines 620-630).

```go
// Client-side configuration retrieval
fetch('/api/v1/auth/oidc/config')
  .then(r => r.json())
  .then(cfg => {
    // cfg.jwks_uri used server-side for verification
    console.log('JWKS URL:', cfg.jwks_uri);
  });

```

### Login Initiation and Nonce Generation

The client requests authentication through either `GET /api/v1/auth/oidc/url` (JSON response) or `GET /api/v1/auth/oidc/start` (HTTP 302 redirect). Both handlers reside in [[`internal/handler/auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go)](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go#L309-L367) and perform critical security setup:

1. Generate a cryptographically secure **nonce**
2. Store the nonce in an HttpOnly cookie named `weknora_oidc_nonce`
3. Build the provider authorization URL via `userService.GetOIDCAuthorizationURL`

```go
// Client-side login initiation
fetch('/api/v1/auth/oidc/url?redirect_uri=https://app.example.com')
  .then(r => r.json())
  .then(res => {
    // Navigate to provider login page
    window.location = res.authorization_url;
  });

```

### Callback Handling and Token Exchange

After provider authentication, the OIDC server redirects to `GET /api/v1/auth/oidc/callback`, handled in [[`internal/handler/auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go)](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go#L385-L460). This handler:

- Extracts the authorization `code` from query parameters
- Exchanges the code for an **ID token** and **access token** via the provider's token endpoint
- Invokes `userService.verifyOIDCIDToken` to perform cryptographic validation
- Redirects to the frontend with either `oidc_result=<payload>` or `oidc_error=<message>` fragments

## Cryptographic Verification of ID Tokens

The JWKS verification implementation in [[`internal/application/service/user.go`](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go)](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go#L1993-L2008) serves as the security gatekeeper before tenant provisioning occurs.

### JWKS Retrieval and Caching

The `verifyOIDCIDToken` method retrieves the JWKS URL from the cached `OIDCAuthConfig`. To prevent repeated HTTP requests, WeKnora implements `oidcJWKCache` within the user service, caching the JSON Web Key Set for a configurable duration. If the cache misses or expires, the system downloads the JWKS set using `net/http`.

### Signature Validation Logic

The verification process extracts the `kid` (Key ID) claim from the ID token header and locates the matching JWK in the retrieved set. The system constructs the appropriate public key (RSA or ECDSA) and validates the JWT signature:

```go
func (s *userService) verifyOIDCIDToken(ctx context.Context, cfg *config.OIDCAuthConfig, rawToken string) (*oidc.IDToken, error) {
    // 1. Fetch or reuse cached JWKS
    jwks, err := s.jwksFetcher.Fetch(ctx, cfg.JWKSURL)
    if err != nil { return nil, err }

    // 2. Parse token header to extract Key ID
    parsed, _ := jwt.ParseSigned(rawToken)
    kid := parsed.Headers[0].KeyID

    // 3. Locate matching JWK
    key := jwks.Key(kid)
    if key == nil { 
        return nil, fmt.Errorf("no matching JWK for kid %s", kid) 
    }

    // 4. Verify signature and extract claims
    var claims map[string]interface{}
    if err := parsed.Claims(key, &claims); err != nil {
        return nil, err
    }
    
    // Additional validation: iss, aud, exp, iat, nbf...
    return &oidc.IDToken{Claims: claims}, nil
}

```

### Standard Claims Verification

Beyond signature validation, the implementation verifies standard OIDC claims including `iss` (issuer), `aud` (audience), `exp` (expiration), `iat` (issued at), and `nbf` (not before). If any validation fails, the function returns an error immediately, aborting the login process before tenant logic executes.

## Tenant Provisioning Integration

Successful JWKS verification triggers the tenant lifecycle management phase, tightly coupled to the authentication result.

### User Identity Resolution

Following successful verification, `resolveOIDCIDUserInfo` (lines approximately 2030-2060 in the same service file) decodes the verified token payload into a `oidcUserInfo` struct. This extracts the unique subject identifier (`sub`), email address, and display name from the cryptographically validated claims.

### Automatic Tenant Creation and Association

The `SwitchTenantForUser` method (implemented around lines 1647-1705 in [[`internal/application/service/user.go`](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go)](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go#L1647-L1705)) executes the provisioning logic:

1. Checks the `tenants` table (defined in [[`internal/types/tenant.go`](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant.go)](https://github.com/Tencent/WeKnora/blob/main/internal/types/tenant.go)) for existing tenant associations
2. If no tenant exists, invokes `tenantService.CreateTenant` to provision a default tenant
3. Links the verified user (identified by `sub` claim) to the tenant
4. Returns the tenant context for session establishment

This coupling ensures **tenant creation only occurs after cryptographic proof of identity**. The handler's final redirect includes the tenant context in the `oidc_result` fragment, allowing the frontend to establish a session with proper organizational scoping.

## Security Mechanisms

### Replay Attack Prevention via Nonce Validation

The system protects against replay attacks through nonce validation implemented in `decodeOIDCState` within [[`internal/handler/auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go)](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go#L471-L489). The nonce generated during login initiation and stored in the `weknora_oidc_nonce` HttpOnly cookie is compared against the `nonce` claim inside the ID token. Mismatches result in immediate authentication failure, preventing token reuse across sessions.

### Key Rotation Handling

The JWKS caching strategy accommodates provider key rotation by respecting cache TTLs while allowing fresh fetches when keys change. If verification fails due to key mismatch (typically indicating rotation), the system can refresh the JWKS cache and retry validation, though the primary implementation returns errors for signature mismatches to maintain strict security posture.

## Summary

- **JWKS verification occurs inside the login callback** at `GET /api/v1/auth/oidc/callback` in [`internal/handler/auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go), not as a background process, ensuring immediate validation before resource allocation
- **Tenant provisioning is strictly dependent** on successful `verifyOIDCIDToken` completion, as implemented in [`internal/application/service/user.go`](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go) lines 1647-1705
- **Cryptographic validation** includes signature verification against RSA/ECDSA keys from the provider JWKS endpoint, plus standard claim validation (`iss`, `aud`, `exp`)
- **Security hardening** includes nonce validation via HttpOnly cookies and configurable JWKS caching to balance performance with key rotation support
- **Endpoint registration** occurs in [`internal/router/routes_auth_tenant.go`](https://github.com/Tencent/WeKnora/blob/main/internal/router/routes_auth_tenant.go) (lines 220-224), mounting the OIDC handlers under `/api/v1/auth/oidc/*`

## Frequently Asked Questions

### What happens if the JWKS endpoint is unavailable during login?

If the JWKS endpoint returns an error or timeout during the `verifyOIDCIDToken` execution, the authentication flow aborts immediately. The callback handler redirects to the frontend with `oidc_error=invalid_token` or similar error indication, and no tenant provisioning occurs. The system relies on the cached `oidcJWKCache` to provide resilience, but verification fails if the cache lacks the required key identifier (`kid`).

### How does WeKnora handle OIDC provider key rotation?

The `oidcJWKCache` implementation caches JWKS data for a configurable duration. When a token arrives signed with a new key (indicated by an unrecognized `kid`), the verification fails initially. The specific implementation in [`internal/application/service/user.go`](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go) fetches fresh JWKS data when cache misses occur, allowing seamless key rotation without service restarts, provided the cache TTL is appropriately configured.

### Can a user authenticate without triggering tenant provisioning?

No. According to the source code in [`internal/application/service/user.go`](https://github.com/Tencent/WeKnora/blob/main/internal/application/service/user.go), the `SwitchTenantForUser` call follows immediately after successful `verifyOIDCIDToken` and `resolveOIDCUserInfo` execution within the callback handler flow. The tenant check (existence or creation) is an integral part of the authentication completion process, though existing users simply link to their current tenant rather than creating new ones.

### Where is the OIDC nonce validated in the WeKnora codebase?

The nonce validation occurs in the `decodeOIDCState` function within [`internal/handler/auth.go`](https://github.com/Tencent/WeKnora/blob/main/internal/handler/auth.go) (approximately lines 471-489). This function compares the nonce stored in the `weknora_oidc_nonce` HttpOnly cookie against the `nonce` claim extracted from the ID token payload. This validation prevents CSRF and replay attacks by ensuring the token returned matches the specific authentication request initiated by the client.