Phoenix auth issues short-lived access JWTs and longer-lived refresh tokens. The Rise SDK can manage the session, attach bearer tokens to authenticated requests, refresh before expiry, and persist the rotated token pair.
Lifecycle
- Request a challenge from Phoenix.
- Sign the challenge with the authority wallet.
- Exchange the signature for an auth response.
- Use
Authorization: Bearer <access_token> on authenticated routes.
- Refresh with
POST /v1/auth/refresh before the access token expires.
- Reauthenticate when refresh fails with a terminal auth error.
The auth response shape is:
{
"token_type": "Bearer",
"access_token": "eyJ...",
"expires_in": 900,
"refresh_token": "eyJ...",
"refresh_expires_in": 2592000
}
Store the full response. Refresh rotates the access token and refresh token; the previous access token is accepted for a short grace window.
Endpoints
| Step | Endpoint | Body or query | Notes |
|---|
| Wallet nonce | GET /v1/auth/nonce?wallet_pubkey=... | query wallet_pubkey | Returns nonce_id, message, and expires_at. |
| Wallet login | POST /v1/auth/login/wallet | wallet_pubkey, signature, nonce_id | signature signs the exact nonce message. |
| Wallet transaction challenge | POST /v1/auth/wallet/transaction-challenge | wallet_pubkey | Alternative for wallets that cannot sign arbitrary messages. |
| Wallet transaction login | POST /v1/auth/login/wallet/transaction | wallet_pubkey, nonce_id, signed_transaction | Exchanges the signed memo transaction for JWTs. |
| Refresh | POST /v1/auth/refresh | refresh_token | SDKs include the current bearer token when available and store the rotated session. |
| Logout | POST /v1/auth/logout | none | Requires Authorization: Bearer <access_token> and revokes the session. |
Wallet login
This TypeScript example is written for a browser wallet that supports signMessage.
import {
LocalStorageAuthSessionStorage,
createPhoenixClient,
} from "@ellipsis-labs/rise";
import bs58 from "bs58";
const client = createPhoenixClient({
apiUrl: "https://perp-api.phoenix.trade",
auth: true,
authConfig: {
storage: new LocalStorageAuthSessionStorage(),
},
ws: false,
});
const auth = client.auth!;
const walletPubkey = wallet.publicKey.toString();
const nonce = await auth.getWalletNonce(walletPubkey);
const message = new TextEncoder().encode(nonce.message);
const signature = await wallet.signMessage(message);
const session = await auth.loginWithWalletSignature(
walletPubkey,
bs58.encode(signature),
nonce.nonce_id
);
console.log(session.accessToken);
In Rust builds with the solana-keypair feature, auth.login_with_wallet_keypair(&keypair).await? wraps the nonce, signing, and login sequence.
Refresh
With a managed SDK session, refresh is normally automatic before authenticated HTTP and WebSocket requests. Manual refresh is still useful when you want to force rotation after loading a saved session.
const auth = client.auth!;
const manager = client.sessionManager!;
const current = await manager.getSession();
if (!current) {
throw new Error("No Phoenix auth session");
}
const refreshed = await auth.refresh(current.refreshToken);
console.log(refreshed.accessToken);
Terminal refresh failures mean the user must sign in again:
invalid_refresh_token
refresh_expired
session_missing
Auth errors
Auth errors use the standard API error shape:
{
"error": "invalid_access_token"
}
Common unauthenticated or reauthentication cases:
| Error code | Status | Meaning |
|---|
missing_access_token | 401 | The route requires an authenticated access token, but none was available to the route guard. |
missing_bearer_token | 401 | The route expects an Authorization: Bearer ... header. |
invalid_access_token | 401 | The access JWT is malformed, expired, signed by an unknown key, or otherwise failed verification. |
access_token_expired | 401 | The access token is expired. Refresh and retry. |
session_missing | 401 or 404 | The server-side session was revoked, expired, or missing. Reauthenticate. |
access_jti_mismatch | 401 | The access token is no longer the current token for the session. Refresh or reauthenticate. |
invalid_refresh_token | 401 | The refresh token is invalid, expired, or already consumed. Reauthenticate. |
refresh_expired | SDK-side | The SDK knows the stored refresh token is past its expiry. Reauthenticate. |
no_auth_session | SDK-side | Auth was enabled, but no session is loaded. Sign in first. |
user_only | 403 | The route requires user auth. |
admin_only | 403 | The route requires admin auth. |
insufficient_role | 403 | The authenticated role cannot access the route. |