> ## 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 a Private Key

> Mint a Dynamic user JWT with a private-key signature — fully autonomously with an agent signing token, or from the user's wallet.

Use private-key sign-in when the sign-in is authorized by a signature over a sign-in message — currently SIWE (Sign-In With Ethereum), so the signing address must be an EVM address. The signer is usually the agent itself — a fully autonomous sign-in with an **agent signing token**: a secp256k1 private key that the agent holds, which serves as its identity. SIWE is only the mechanism; the agent signing token is the credential. The signer can also be a human user signing in their own wallet UI. For the human-relayed alternative, see [Sign In with Email OTP](https://docs.dynamic.xyz/docs/node/agents/sign-in-with-email-otp.md); if your agents already have identities in your own auth system, see [Sign In with an External JWT](https://docs.dynamic.xyz/docs/node/agents/sign-in-with-an-external-jwt.md).

> [!NOTE]
>
> Before this: read the [Agent Wallets overview](https://docs.dynamic.xyz/docs/node/agents/overview.md) and install `@dynamic-labs-wallet/node`.

## 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.

## Create the auth client

`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. Set `appOrigin` to the origin your sign-ins present as; it must be on your environment's allowlisted CORS origins.

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

const auth = createAuthClient({
  environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  appOrigin: 'https://yourapp.example.com',
});
```

## Sign in with `wallet.signIn`

`wallet.signIn` runs the whole flow in one call — it fetches a nonce, builds the sign-in message (deriving its domain and URI from `appOrigin`; pass `domain` and `uri` per call to override), collects the signature from your `signMessage` callback, and verifies it to mint the JWT. The callback is the only difference between the two modes: the agent signs with its own key, or a human signs in your app's UI.

> [!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.

> [tab] Autonomous agent
>
> No human in the loop: the agent signs with its agent signing token. If the agent does not have one yet, generate it once and store it in your secrets vault or KMS:
>
> ```typescript theme={"system"}
> import { generatePrivateKey } from 'viem/accounts';
>
> const agentSigningToken = generatePrivateKey(); // 32-byte secp256k1 private key, 0x-prefixed hex
> // One-time setup: store it in your secrets vault or KMS. Generating a new one
> // creates a new Dynamic user — it will not see the previous user's wallets.
> ```
>
> On every run, load the stored token and sign in with it:
>
> ```typescript theme={"system"}
> import { privateKeyToAccount } from 'viem/accounts';
>
> const agentAccount = privateKeyToAccount(process.env.AGENT_SIGNING_TOKEN as `0x${string}`);
>
> const { jwt, expiresAt } = await auth.wallet.signIn({
>   address: agentAccount.address,
>   signMessage: (message) => agentAccount.signMessage({ message }),
>   sessionPublicKey: publicKeyHex,
> });
> ```
>
> The Dynamic user is keyed to this public address, so use the same agent signing token every run to keep operating the same user's wallets — and re-authentication at the refresh limit needs no human either. For custody requirements, see [Storage & Security](https://docs.dynamic.xyz/docs/node/agents/storage-and-security.md).

> [tab] Human user
>
> The user signs with their own wallet (for example, in your app's UI) — how you collect that signature is your implementation:
>
> ```typescript theme={"system"}
> // The public address of the wallet that will sign the message.
> const walletAddress = '0xUserPublicAddress';
>
> const { jwt, expiresAt } = await auth.wallet.signIn({
>   address: walletAddress,
>   signMessage: (message) => collectSignatureFromUser(message), // your implementation
>   sessionPublicKey: publicKeyHex,
>   statement: 'Authorize this agent to use your Dynamic wallet',
> });
> ```
>
> In the JS client SDK this whole flow is a single `connectAndVerifyWithWalletProvider()` call, because the SDK is connected to the user's wallet and signs internally. Server-side there is no wallet provider, so collecting the user's signature is your application's job.

Sign-in resolves to `{ jwt, expiresAt, userId }`. Sign-in fails with a descriptive error when `address` is not a 0x-prefixed 20-byte hex address, when neither `appOrigin` nor `domain`/`uri` is provided, when the signature or nonce is invalid or expired, 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).

For custom message construction, `auth.wallet` also exposes the underlying primitives — `getNonce`, `createMessage`, and `verify`. See the [Wallet sign-in reference](https://docs.dynamic.xyz/docs/node/reference/auth/wallet-sign-in.md).

## 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 JWT (update it on every refresh). 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

* [Wallet sign-in](https://docs.dynamic.xyz/docs/node/reference/auth/wallet-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.