# Kinde

> Kinde AuthStrategy — verifies Kinde-issued JWTs via JWKS at the configured issuer (no client secret) and maps org_code → tenantId.



---

<!-- source: en/plugins/auth-kinde.md -->
## Kinde

_Kinde AuthStrategy — verifies Kinde-issued JWTs via JWKS at the configured issuer (no client secret) and maps org_code → tenantId._

`@voltro/plugin-auth-kinde` integrates [Kinde](https://kinde.com): an `AuthStrategy` (conforming to `@voltro/protocol`) that verifies Kinde-issued JWTs against the issuer's **JWKS endpoint** (public-key verify — no client secret), maps Kinde's `org_code` to the framework's `tenantId`, and composes with other strategies (your password cookie, an API key, another IdP). It's a thin specialization of the shared `jwtBearerStrategy` — it adds Kinde's defaults and performs no verification of its own. Verified claims arrive on the Subject as `metadata.provider === 'kinde'` + `metadata.claims`.

## Install

```
pnpm add @voltro/plugin-auth-kinde
```

## Wiring

Add `kindeStrategy(...)` to `auth.strategies` in your api's `app.config.ts`. The built-in signed-cookie password strategy always runs first; your strategies run after it, in order, until one `matched`s (or one `failed`s).

```ts
// app.config.ts
import { kindeStrategy } from '@voltro/plugin-auth-kinde'

export default {
  type: 'api' as const,
  name: 'myApi',
  store: 'postgres' as const,
  auth: {
    strategies: [
      kindeStrategy({
        issuer:   'https://yourcompany.kinde.com',   // required
        // audience: '…',                             // your Kinde API audience
        // defaultTenantId: 'public',                 // fallback for single-tenant setups
        // scopesFromClaims: (c) => c.permissions ?? [],  // Kinde permissions → scopes
      }),
    ],
  },
}
```

`issuer` is the only required option (trailing slash is normalised). From it the JWKS URL defaults to `<issuer>/.well-known/jwks`. The cookie defaults to `kinde_access_token` (for apps that mirror the SDK's token into a cookie for SSR; pass `null` for header-only).

Tenant resolution maps from `claims.org_code`, falling back to `claims.org_codes[0]`, then `defaultTenantId`. Supply `tenantIdFromClaims(claims)` to derive it (returning `null` is a `failed` verdict). `scopesFromClaims` maps verified claims onto `Subject.scopes` — Kinde emits a `permissions` array and a `roles` list (e.g. `roles.map((r) => r.key)`).

To accept your own password sessions AND Kinde during a migration, build the chain explicitly with `composeAuthStrategies` (see [auth strategies](/docs/authentication/strategies)):

```ts
import { composeAuthStrategies } from '@voltro/protocol'
import { voltroPasswordStrategy } from '@voltro/plugin-auth'
import { kindeStrategy } from '@voltro/plugin-auth-kinde'

const resolve = composeAuthStrategies([
  voltroPasswordStrategy(),
  kindeStrategy({ issuer: process.env.KINDE_ISSUER! }),
])
```

## Options

| Option | Default | Notes |
|---|---|---|
| `issuer` | — (**required**) | e.g. `https://acme.kinde.com`. Builds JWKS URL. |
| `audience` | (off) | Your Kinde API audience. |
| `jwksUrl` | `<issuer>/.well-known/jwks` | Override. |
| `algorithms` | `['RS256']` | Allowlist; override for a non-default Kinde signing alg. |
| `cookieName` | `'kinde_access_token'` | `null` for header-only. |
| `tenantIdFromClaims` | (uses `org_code` → `org_codes[0]`) | Derive `tenantId`; `null` → `failed`. |
| `defaultTenantId` | (none) | Fallback for single-tenant setups. |
| `scopesFromClaims` | (none) | Map claims → `Subject.scopes`. |

## Environment variables

None read directly — pass `issuer` / `audience` explicitly (typically from your own `defineEnv`-declared vars).

## Security

Server-only; fails closed — a forged, expired, or wrong-audience token, or one with no resolvable tenant, yields `failed`, never `matched`. JWKS public-key verify only; no Kinde client secret is ever handled.

## See also

- [External identity providers](/docs/authentication/external-idp)
- [Auth plugin](/docs/plugins/auth)
