> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign In with an External JWT

> Mint a Dynamic user JWT for your agent by exchanging a JWT from your own auth provider, binding the session key at sign-in.

Use external JWT sign-in when each agent is a first-class identity in your own identity or authorization system: your issuer signs a JWT attesting to the agent's identity, and the agent exchanges it for a Dynamic user JWT. This is the integration point for external identity systems — each agent identity your issuer attests becomes its own Dynamic user with its own MPC wallets, rather than a fleet of agents sharing one user or one credential. For the other paths, see [Sign In with Email OTP](https://docs.dynamic.xyz/docs/node/agents/sign-in-with-email-otp.md) (human relays a code) or [Sign In with a Private Key](https://docs.dynamic.xyz/docs/node/agents/sign-in-with-a-private-key.md) (a signature authorizes the sign-in).

> [!NOTE]
>
> Before this: read the [Agent Wallets overview](https://docs.dynamic.xyz/docs/node/agents/overview.md), install `@dynamic-labs-wallet/node`, and configure [third-party auth](https://docs.dynamic.xyz/docs/auth/bring-your-own-auth.md) for your environment in the Dynamic Console (issuer, JWKS URL, audience). Bring Your Own Auth is an enterprise feature.

## Generate a session key pair

```typescript theme={"system"}
import { generateSessionKeyPair } from '@dynamic-labs-wallet/node';

const { publicKeyHex, privateKeyJwk } = await generateSessionKeyPair();
// Store privateKeyJwk in your secrets vault. The SDK does not persist it.
```

`generateSessionKeyPair` returns a P-256 key pair: `publicKeyHex` (33-byte compressed public key, lowercase hex) and `privateKeyJwk` (the private key as a JWK). The SDK stores nothing — you persist the JWK in your secrets vault.

> [!TIP]
>
> The returned JWK is extractable so it can be handed back to you — it is a convenience, not a security boundary. For non-extractable custody, skip `generateSessionKeyPair`: generate a P-256 key in your KMS, HSM, or enclave, pass its compressed public key hex at sign-in, and back `getSessionSignature` with the external signer.

## Sign in with `externalJwt.signIn`

`createAuthClient` performs Dynamic sign-in flows headlessly — the server-side counterpart to the JS client SDK's sign-in. It holds configuration only: no secrets, no persistence. Obtaining the external JWT is your application's job: the SDK never mints or verifies it — you supply it, it is used once for the exchange, and it is never persisted or logged. Dynamic verifies it server-side against the environment's configured issuer, JWKS URL, and audience.

> [!WARNING]
>
> Pass `sessionPublicKey` at sign-in. This binds the session key into the minted JWT and is what makes the signed-session proof verifiable. If you omit it, wallet creation and signing with `backUpToDynamic: true` fail at the backup and recovery calls. This is the most commonly missed step.

```typescript theme={"system"}
import { createAuthClient } from '@dynamic-labs-wallet/node';

const auth = createAuthClient({ environmentId: process.env.DYNAMIC_ENVIRONMENT_ID! });

// A JWT signed by your configured external issuer, attesting to the agent's identity.
const { jwt, expiresAt } = await auth.externalJwt.signIn({
  jwt: externalJwtFromYourIssuer,
  sessionPublicKey: publicKeyHex,
});
```

Each distinct `sub` claim in your issuer's JWTs is a distinct Dynamic user with its own wallets, so keep an agent's subject stable across runs to keep operating the same user's wallets — and give each agent its own subject to keep their wallets separate. Re-authentication at the refresh limit is another exchange with a fresh JWT from your issuer — programmatic if your issuer mints programmatically, so no human is required. This also keeps each agent's lifecycle in your hands: an agent your issuer stops minting JWTs for cannot sign in again once its current session can no longer be refreshed.

Sign-in resolves to `{ jwt, expiresAt, userId }`. `externalJwt.signIn` throws before any network call when `jwt` is blank. Sign-in fails with a descriptive error when the external JWT's signature or claims fail verification, when the environment does not have third-party auth configured (the server responds 422 with `This feature is not enabled`), when the account requires MFA (the headless client does not support MFA), or when the environment uses cookie-based auth (the response carries no JWT in the body).

## What you must persist

The SDK is stateless by design — your store is the source of truth. After sign-in you hold two artifacts to keep safe: the session private key (secrets vault or KMS) and the Dynamic user JWT (update it on every refresh). The external JWT is single use — do not persist it; request a new one from your issuer when you need to sign in again. See [Storage & Security](https://docs.dynamic.xyz/docs/node/agents/storage-and-security.md) for the full persistence matrix.

## Next step

Hand the JWT and the session private key to the wallet client: [Use Agent Wallets](https://docs.dynamic.xyz/docs/node/agents/use-agent-wallets.md).

## API reference

* [External JWT sign-in](https://docs.dynamic.xyz/docs/node/reference/auth/external-jwt-sign-in.md)
* [generateSessionKeyPair](https://docs.dynamic.xyz/docs/node/reference/auth/generate-session-key-pair.md)
* [createAuthClient](https://docs.dynamic.xyz/docs/node/reference/auth/create-auth-client.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.