# Authenticate the wallet client

With a JWT in hand, authenticate the wallet client and operate wallets as the user: create, sign, and keep the session alive.

Before this: a Dynamic user JWT minted with your session public key bound in — see [Sign In with Email OTP](/content/docs/node/agents/sign-in-with-email-otp/index.html), [Sign In with a Private Key](/content/docs/node/agents/sign-in-with-a-private-key/index.html), or [Sign In with an External JWT](/content/docs/node/agents/sign-in-with-an-external-jwt/index.html) — and access to the session private key.

```javascript
import { DynamicEvmWalletClient } from '@dynamic-labs-wallet/node-evm';
import { signSessionMessage } from '@dynamic-labs-wallet/node';

const client = new DynamicEvmWalletClient({
  environmentId: process.env.DYNAMIC_ENVIRONMENT_ID!,
  enableMPCAccelerator: false, // Set to true only on AWS Nitro Enclave-compatible infrastructure
});

await client.authenticateJwt(jwt, {
  getSessionSignature: (message) => signSessionMessage(message, privateKeyJwk),
});
```

`authenticateJwt` installs the JWT as the bearer token for all subsequent calls. There is no token exchange: the client only checks the token’s structure (a compact JWS with three segments); the server validates it on every request.`getSessionSignature` keeps key custody with you. The session private key never enters the SDK — the SDK sends messages to your callback and uses the signatures it returns. `signSessionMessage` implements the callback for a stored JWK; a KMS- or HSM-backed signer works the same way as long as it returns the same encoding.

`getSessionSignature` must return the signature as lowercase hex: the raw 64-byte ECDSA P-256 `r‖s`, exactly as `signSessionMessage` produces. Base64 output corrupts the proof — base64 can contain `/`, which breaks the `/`-delimited composite the SDK sends to the keyshares relay.

# Create wallets and sign

With the signer configured, use the client as usual. When you pass `backUpToDynamic: true`, the SDK automatically attaches a signed-session proof to the backup and recovery calls — it signs the JWT’s session ID and a single-use server nonce with your callback. Without a signer, those calls fail with a 400 from the keyshares relay for agent JWT sessions.

```javascript
import { ThresholdSignatureScheme } from '@dynamic-labs-wallet/core';

const { walletMetadata, externalServerKeyShares } = await client.createWalletAccount({
  thresholdSignatureScheme: ThresholdSignatureScheme.TWO_OF_TWO,
  password: process.env.WALLET_PASSWORD,
  backUpToDynamic: true,
});
// Persist walletMetadata (durable store) and externalServerKeyShares (secrets vault) —
// see Storage & Security for the full persistence matrix.

const signature = await client.signMessage({
  message: 'Hello from your agent',
  walletMetadata,
  externalServerKeyShares,
  password: process.env.WALLET_PASSWORD,
});
```

# Reuse an existing wallet

Creation is a one-time step. On later runs, load the persisted `walletMetadata` and `externalServerKeyShares` from your stores instead of creating a new wallet, and sign directly:

```javascript
const walletMetadata = await loadWalletMetadata(userId); // your durable store
const externalServerKeyShares = await loadKeyShares(userId); // your secrets vault

const signature = await client.signMessage({
  message: 'Hello again from your agent',
  walletMetadata,
  externalServerKeyShares,
  password: process.env.WALLET_PASSWORD,
});
```

Calling `createWalletAccount` on every run creates a new wallet each time. Create once, persist the returned artifacts, and reuse them for the life of the wallet.

# Refresh long-running sessions

User JWTs are short-lived. `refreshAuthToken` refreshes the JWT using the current session, installs the replacement on the client, and returns it so you can persist it. The session signer you passed to `authenticateJwt` is preserved across refreshes.

```javascript
const refreshedJwt = await client.refreshAuthToken();
// Overwrite the stored JWT — the previous token is superseded.
```

Sign-in returns `expiresAt` — schedule refreshes ahead of it instead of waiting for a 401. The server caps how long a session can be extended via the JWT’s `refreshExp` claim. Past that limit, `refreshAuthToken` fails with a 401 and a new sign-in is required — refresh cannot extend a session indefinitely. For a human user, that means re-approving (a new OTP code or SIWE signature). For an autonomous agent with an agent signing token, re-authentication is just another programmatic sign-in.

```javascript
try {
  await client.refreshAuthToken();
} catch {
  // Refresh limit reached or session invalid: run the sign-in flow again,
  // then call authenticateJwt with the new token.
}
```

`refreshAuthToken` also throws a descriptive error when the environment is configured for cookie-based auth: refresh responses then carry no JWT in the body, and agent flows require header-based JWTs.

# Next steps

- [Example Usage](/content/docs/node/agents/example-usage/index.html) — complete runnable scripts for each sign-in path.
- [Storage & Security](/content/docs/node/agents/storage-and-security/index.html) — what to persist, and the security model.

# API reference

- [authenticateJwt](/content/docs/node/reference/auth/authenticate-jwt/index.html)
- [refreshAuthToken](/content/docs/node/reference/auth/refresh-auth-token/index.html)
- [signSessionMessage](/content/docs/node/reference/auth/sign-session-message/index.html)
