# Session login

Authenticate a signed-in user with a wallet challenge, manage session JWTs, and complete MFA for protected partner-app actions.

Use a session when your partner app acts for a signed-in user. Wallet login returns a bearer access token, encoded as a JWT, for that user's account. Use Ed25519 API keys for unattended bots and market-making workloads.

Testnet is the public environment and runs the latest release. Mainnet is coming soon. The examples below use `https://api.testnet.polyester.com`; see [regions and connectivity](https://testnet.polyester.com/docs/developer-docs/getting-started/regions-and-connectivity) for regional API hosts.

## Sign in with a wallet

The wallet must be an externally owned account (EOA) that controls the target smart account. `auth.v1.AuthService` issues the login challenge and verifies the signed response. Signing the login message does not authorize a transaction.

1. Request a challenge

   Call [`CreateWalletChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/CreateWalletChallenge) or [`POST /v1/auth/wallet-challenge`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/wallet-challenge). Supply `smartAccountAddress`, the controlling wallet's `signerAddress`, your app's origin as `uri`, and `purpose: "LOGIN"`.

   The origin includes scheme, host, and an optional port, without a path, query, fragment, or trailing slash. In a browser, it must match the request's `Origin` header. The response contains the canonical EIP-4361 `message` and `expiresAt`. The challenge expires after five minutes.

2. Sign the returned message

   Ask the wallet to sign the exact UTF-8 bytes of `message` using `personal_sign`, with EIP-191 semantics. Do not hash, edit, or reconstruct the message. Present the server-issued message to the user as the login request.

3. Verify the signature and receive a session

   Call [`LoginWithWallet`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/LoginWithWallet) or [`POST /v1/auth/login/wallet`](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/login/wallet) with `smartAccountAddress`, the unchanged `message`, and the wallet's `signature`.

   Success returns `accessToken`, `expiresAt`, `accountId`, `session`, and `username` when one is set. The initial session level is `PRIMARY_AUTHENTICATED`. The challenge is consumed on successful login; request a new challenge after an expired, replaced, or consumed challenge fails.

The API field names above use JSON spelling. Generated Protobuf clients expose the equivalent fields in their language's naming style. Neither challenge creation nor wallet verification requires an existing session.

Login does not accept terms on the user's behalf. After explicit user consent, call [`AcceptTerms`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/AcceptTerms) ([REST](https://testnet.polyester.com/docs/api-docs/rest/POST/v1/auth/terms/accept)). Acceptance is required before creating a Subaccount, API key, or deposit address.

## Send the session token

Use the returned access token in the HTTP header for REST and ConnectRPC requests:

```http
Authorization: Bearer <accessToken>
```

Do not put bearer tokens in URLs or logs. Keep them in protected application storage and send them only to the intended API host over TLS. Anyone holding a token can act with its authority.

Read the current identity and assurance level with [`Me`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/Me) or [`GET /v1/auth/me`](https://testnet.polyester.com/docs/api-docs/rest/GET/v1/auth/me). A session identifies the root account; access to each Subaccount still depends on the user's role and applicable policy. See [Subaccounts and roles](https://testnet.polyester.com/docs/developer-docs/account/subaccounts-and-roles).

## Lifetime and renewal

Session access tokens have a default lifetime of 24 hours. Use the response's `expiresAt` as the expiry for the token you received rather than calculating a deadline from that default. MFA responses return the replacement token's deadline as `accessTokenExpiresAt`.

There is no public refresh-token exchange. When the session expires, repeat the wallet challenge and login flow. Do not invent a refresh endpoint or keep retrying with an expired bearer token.

Successful MFA verification and factor enrollment can return a new MFA-elevated `accessToken`. Replace your stored token and expiry with the returned values. This requires user verification and is not a background refresh flow.

Token expiry and recent MFA are separate deadlines. A session token can still be valid when an operation requires the user to complete MFA again.

## Operations that require a session

The API reference marks session-only methods as "Session token". An API key cannot call these methods, even with a permissive API-key policy. Important groups include:

- **API key administration** · [`CreateApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/CreateApiKey), [`ListApiKeys`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/ListApiKeys), [`GetApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/GetApiKey), [`UpdateApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/UpdateApiKey), and [`DeleteApiKey`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ApiKeyService/DeleteApiKey).\
  Reading key metadata also requires a session. See [API key lifecycle](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-lifecycle).
- **Policy administration** · [`ListApiPolicies`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/ListApiPolicies), [`SetApiKeyPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/SetApiKeyPolicy), and [`SetSubaccountPolicy`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.PolicyService/SetSubaccountPolicy).\
  Reading, creating, updating, deleting, and assigning API-key and Subaccount policies require a session. See [API key policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies).
- **Account and identity settings** · [`AcceptTerms`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/AcceptTerms), [`GetProfile`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ProfileService/GetProfile), and [`ResolveAccount`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.ResolveService/ResolveAccount).\
  Profile changes, username operations, and social verification also require a session.
- **Subaccount administration** · [`CreateSubaccount`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/CreateSubaccount), [`InviteSubaccountMember`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountService/InviteSubaccountMember), and [`GetEffectiveSubaccountPermissions`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.SubaccountRoleService/GetEffectiveSubaccountPermissions).\
  Creation challenges, updates, member lists, role changes, removals, invite responses, and member-MFA settings require a session. This does not make every Subaccount read session-only.
- **Saved destinations** · [`CreateAddressBookEntry`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AddressBookService/CreateAddressBookEntry) and [`CreateAddressBookTag`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AddressBookService/CreateAddressBookTag).\
  Entry creation, updates, deletion, copying, and tag mutations require a session. Authorized address-book reads can accept API keys.
- **MFA** · [`ListMFAFactors`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/ListMFAFactors) and [`BeginMFAChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/BeginMFAChallenge).\
  Factor enrollment, challenges, factor management, and recovery-code rotation require an interactive session.

These examples are not a replacement for each operation's authentication requirements. Wallet-based withdrawals and auto-withdraw rule changes also use sessions. Conversely, [`Me`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.AuthService/Me) accepts either a session or an API key. Order-entry trade WebSockets require API keys.

An API key sent to a session-only method receives `AUTH_SESSION_KIND_NOT_ALLOWED` with HTTP `403`. REST returns a problem response; ConnectRPC returns `permission_denied` with an `auth.v1.AuthErrorDetail`. Authenticate with a user session instead of retrying the same key. See [error responses](https://testnet.polyester.com/docs/developer-docs/error-handling/error-responses) and [error codes](https://testnet.polyester.com/docs/developer-docs/error-handling/error-codes).

## Complete step-up MFA

Primary wallet login does not satisfy every protected operation. A request can require a recent MFA-elevated session or a separate fresh proof for one protected action.

For `AUTH_MFA_ELEVATION_REQUIRED`, begin an MFA challenge with purpose `MFA_CHALLENGE_PURPOSE_SESSION_ELEVATION`. For `AUTH_STEP_UP_REQUIRED`, use `MFA_CHALLENGE_PURPOSE_FRESH_STEP_UP` instead.

Call [`BeginMFAChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/BeginMFAChallenge), then complete the available factor with [`VerifyTOTPChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/VerifyTOTPChallenge), [`FinishPasskeyChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/FinishPasskeyChallenge), or [`VerifyRecoveryCodeChallenge`](https://testnet.polyester.com/docs/api-docs/connect/auth.v1.MFAService/VerifyRecoveryCodeChallenge). Replace the bearer token with the returned `accessToken`.

A fresh-step-up response also supplies `stepUpToken` and `stepUpExpiresAt`. Send that proof in `X-Auth-Step-Up` alongside the bearer access token on the protected request. The proof is bound to the account and session, and is for one protected action.

> **Keep both credentials in their headers**
>
> The step-up token does not replace `Authorization: Bearer <accessToken>`. Do not reuse a fresh proof for a different action or across independent requests.

API key mutations, policy mutations, factor deletion, and recovery-code rotation are examples that need fresh step-up. Requirements for other actions can depend on enrollment or the user's role. Follow [multi-factor authentication](https://testnet.polyester.com/docs/developer-docs/authentication-security/multi-factor-authentication) for enrollment, factor availability, and operation-specific requirements.

## Log out

Stop authenticated requests, close the app's private streaming connections, and remove the access token, step-up proof, and connection and subscription tokens from memory and persistent storage. Prevent pending work from restoring a token or reopening a connection after logout.

There is no public logout or session-revocation operation. Clearing your app's credentials does not invalidate copies of an issued bearer token; treat them as usable until their expiry. Disconnecting a wallet also does not revoke a JWT already issued to the app.

## Choose sessions or API keys

Choose sessions for signed-in user experiences, account administration, consent, and MFA. Your app must handle token expiry and let the user complete wallet and MFA prompts.

Choose [Ed25519 API keys](https://testnet.polyester.com/docs/developer-docs/authentication-security/ed25519-api-keys) for unattended trading. Restrict each workload with [API key policies](https://testnet.polyester.com/docs/developer-docs/account/api-key-policies) and [IP allowlists](https://testnet.polyester.com/docs/developer-docs/authentication-security/api-key-ip-allowlists). Keep the administrative session separate from the bot's credentials.
