/ Docs

Authentication

Manage Volcano user accounts, sessions, email flows, and OAuth from Python.

Sign in with a project's anonymous key and an existing user's credentials. Enable the required authentication methods for the project first. Install the SDK using the quickstart.

import os

from volcano_sdk import VolcanoClient

client = VolcanoClient(
    anon_key=os.environ["VOLCANO_ANON_KEY"],
    api_url=os.environ.get("VOLCANO_API_URL", "https://api.volcano.dev"),
)
session = client.auth.sign_in(
    email=os.environ["VOLCANO_USER_EMAIL"],
    password=os.environ["VOLCANO_USER_PASSWORD"],
)
user = client.auth.get_user()
assert user.id == session.user_id
print(user.email)

Use a separate client for each independent user session. The examples below describe separate account workflows using this client. Keep access and refresh tokens out of logs; the SDK stores sessions in memory only.

Create an account

sign_up() returns an immutable acknowledgement without changing the session by default. The signup acknowledgement is identical for new and existing email addresses. Pass sign_in_when_allowed=True to follow it with sign_in() only when confirmation is not required:

result = client.auth.sign_up(
    email="new-user@example.com",
    password="correct-horse-battery-staple",
    sign_in_when_allowed=True,
)
session = result.session  # None when no follow-up sign-in ran.

A successful follow-up stores the session and emits the normal sign-in event. A failed follow-up raises its usual typed error; it does not undo the successful signup.

Read the local session and user

get_session() reads immutable local state. It does not refresh or validate the token.

Sessions returned by authentication retain the user payload in session.user, including metadata. The snapshot is deeply immutable and available without a request. It is cached data, not proof of authentication; use auth.get_user() to fetch the server-validated profile. Existing three-field Session construction still works, with user=None. An adopted snapshot must have the same user ID. Successful get_user(), update_user(), convert_anonymous(), and confirm_email_change() calls update that local snapshot. Automatic HTTP 401 recovery can rotate credentials and emit TOKEN_REFRESHED; the profile update itself does not. Previously returned sessions remain immutable. Profile identity checks compare UUID values; the session retains its original user ID spelling, including in the cached snapshot.

Validate and update a profile

get_user() sends the active access token to Volcano and returns an immutable, server-validated profile with the complete public AuthUser fields. Profile timestamps are timezone-aware datetime values, and nested user and application metadata are immutable. The request updates the cached profile without changing credentials unless HTTP 401 recovery requires a refresh. Successful recovery rotates credentials and emits TOKEN_REFRESHED. If another authentication operation replaces the session while the request is in flight, get_user() raises SessionChangedError instead of returning a profile for stale credentials.

user = client.auth.update_user(metadata={"display_name": "Ada", "avatar": None})
print(user.id)

update_user() updates the current user's password, metadata, or both. Metadata is a shallow patch: omitted keys remain unchanged, and setting a key to None removes it. The method returns the same immutable profile type as get_user() and updates the cached profile without changing credentials unless HTTP 401 recovery requires a refresh. Successful recovery rotates credentials and emits TOKEN_REFRESHED. It also rejects a response if another authentication operation replaces the session while the update is in flight.

Recover a password

Request a password reset email without creating or changing a session:

client.auth.reset_password_for_email(email="user@example.com")

When transactional email is configured, Volcano sends the reset link. Success returns None, and the response is intentionally identical whether or not the email belongs to an account. Failures raise the same typed Volcano errors as other authentication operations.

Confirm an email address

Confirm an email address with the token from its confirmation link:

client.auth.confirm_email(token="confirmation-token")

Success returns None. Confirmation does not sign in the confirmed account or change an unrelated local session.

Resend confirmation

Request another confirmation email without revealing account state:

client.auth.resend_confirmation(email="user@example.com")

Success returns None whether the account is unknown, already confirmed, or eligible. Volcano sends mail only for an existing unconfirmed account when transactional email is configured. Rate limits raise RateLimitedError with retry_after when the server supplies it.

Change an email address

Request confirmation for a new email address while keeping the current session:

result = client.auth.request_email_change(new_email="new@example.com")
print(result.new_email)

The immutable result contains the server acknowledgement. Its message and new_email fields may be None. The request fails if there is no active session or that session changes in flight.

Cancel the pending change while keeping the current session:

client.auth.cancel_email_change()

Success returns None. A successful stale response is rejected if another authentication operation replaces the session while cancellation is in flight.

Confirm the pending change with the token delivered to the new address:

user = client.auth.confirm_email_change(token="email-change-token")
print(user.email)

The method returns the immutable updated user without replacing the active session. A successful stale response is rejected if another authentication operation replaces that session in flight.

List server sessions

List sessions using the stable activity-ordered offset pagination:

page = client.auth.list_sessions(page=1, limit=20)
for session in page.sessions:
    print(session.id, session.user_agent, session.is_current)

The method returns immutable SessionPage and AuthSession values. It raises SessionChangedError instead of returning a page for a session that was replaced while the request was in flight. Sort, filter, and cursor controls are not yet exposed by this facade.

Use hosted authentication

Build a managed hosted-auth URL:

import secrets

hosted_state = secrets.token_urlsafe(32)
hosted_url = client.auth.get_hosted_auth_url(
    project_id="00000000-0000-4000-8000-000000000020",
    action="signup",
    state=hosted_state,
)

Store hosted_state in the user's signed server-side session before redirecting to hosted_url. In the callback, atomically fetch and delete the stored state before validation, even if validation or adoption fails. Reject a missing or already-consumed state. After parsing the returned fragment into a Session, validate and adopt it:

session = client.auth.adopt_hosted_auth_session(
    returned_session,
    state=returned_state,
    expected_state=hosted_state,
)

The SDK rejects a mismatched state before changing local authentication. It builds and adopts the flow but does not parse browser URLs, navigate, or persist state. The action deep link applies to Volcano's built-in page; a customized login page must implement its own signup or forgot-password flow.

Sign in with OAuth

Build the URL that starts an OAuth sign-in flow:

import secrets

oauth_state = secrets.token_urlsafe(32)
authorization_url = client.auth.sign_in_with_oauth(
    provider="github",
    redirect_to="https://app.example.com/auth/callback",
    state=oauth_state,
)

Store oauth_state in the user's signed server-side session, then redirect the user to the returned URL. In the callback, atomically fetch and delete the stored nonce as stored_oauth_state; reject a missing or already-consumed nonce. Pass the returned and consumed states to the SDK so it rejects login CSRF before exchanging the one-time code:

session = client.auth.exchange_oauth_code(
    code=callback_code,
    redirect_to="https://app.example.com/auth/callback",
    state=callback_state,
    expected_state=stored_oauth_state,
)

The callback URL must exactly match a registered project redirect. The exchange stores the returned Volcano session on the client. The SDK does not open a browser or persist OAuth state between requests; use your framework's signed session or equivalent storage for that state.

List the OAuth providers linked to the current account:

providers = client.auth.list_linked_oauth_providers()
for provider in providers:
    print(provider.provider, provider.linked_at)

The method returns an immutable tuple of LinkedOAuthProvider values and raises SessionChangedError if the active session changes while the request is in flight.

Start linking another OAuth provider to the current account:

authorization_url = client.auth.link_oauth_provider(provider="github")

Redirect the user to the returned URL to complete the provider flow. The method accepts apple, github, google, or microsoft and raises SessionChangedError if the active session changes while the request is in flight.

Unlink an OAuth provider from the current account:

client.auth.unlink_oauth_provider(provider="github")

The server rejects removal of the account's only authentication method. A successful stale response raises SessionChangedError instead of acknowledging work authorized by a replaced session.

Use provider APIs

Check whether Volcano has a valid server-held provider token:

status = client.auth.get_oauth_provider_token(provider="github")
print(status.provider, status.expires_in)

The immutable OAuthProviderTokenStatus contains provider and expiry metadata, not the credential. Volcano refreshes an expired token on the server. A stale result raises SessionChangedError.

Refresh a provider token explicitly:

status = client.auth.refresh_oauth_provider_token(provider="github")
print(status.provider, status.expires_in)

The refresh credential and new access token remain on the server. A stale result raises SessionChangedError.

Call a provider API through Volcano's fixed-host server proxy:

repos = client.auth.call_oauth_api(
    provider="github",
    endpoint="/user/repos",
)
print(repos[0]["name"])

The method returns an immutable copy of the provider's JSON value. Volcano owns token refresh and host validation. A stale result raises SessionChangedError.

Revoke other sessions

Sign out every other device while keeping the current session active:

client.auth.delete_all_other_sessions()

Success returns None. Do not replace the client's session while this request is in flight: the server may revoke that replacement as an "other" session. If replacement occurs, the method raises SessionChangedError instead of acknowledging a stale result.

Revoke one session

Revoke one session by ID:

client.auth.delete_session(session_id="00000000-0000-4000-8000-000000000099")

The request uses the current access token. When its JWT contains a readable UUID session_id, deleting that session clears local credentials even if the request outcome is uncertain. Without that identifier, the SDK cannot recognize self-deletion. Deleting another session does not itself clear local state. HTTP 401 recovery can rotate credentials and emit TOKEN_REFRESHED; a server-rejected refresh clears the captured session before the operation raises. If another authentication operation replaces the session before deletion finishes, the method raises SessionChangedError instead of clearing the replacement or acknowledging a stale result.

Use anonymous accounts

Create an anonymous account and make its tokens the current session:

session = client.auth.sign_in_anonymously(metadata={"device": "mobile"})

Anonymous sign-ins must be enabled for the project. Convert the account before signing out if the user needs to recover it later.

Attach email credentials while preserving the anonymous user's ID:

user = client.auth.convert_anonymous(
    email="user@example.com",
    password="a-long-example-password-2026",
    metadata={"display_name": "Ada"},
)

When email confirmation is required, confirm the new address before treating it as verified.

Complete a password reset

Set a new password with the recovery token from that email:

client.auth.reset_password(
    token="recovery-token",
    new_password="new-correct-horse-battery-staple",
)

Success returns None. The reset revokes the recovered account's existing sessions and does not sign it in. The client keeps any unrelated local session unchanged; sign in with the new password when the reset flow completes.

Use supplied credentials

To start with only a supplied user access token, pass access_token to VolcanoClient. Construction makes no request and leaves refresh_token, user_id, and user as None until supplied or validated by the server. get_user() validates and caches the profile without changing credentials unless HTTP 401 recovery requires a refresh. Successful recovery rotates credentials and emits TOKEN_REFRESHED. Without a refresh token, refresh_session() raises AuthenticationError and sign_out() clears local state and revokes the server session when the access JWT contains a readable UUID session_id. Supplied credentials require both a refresh token and an access JWT with a readable UUID session_id to enable refresh. See the token bootstrap example.

Adopt an existing session

Copy a complete native session into another client's memory:

session = source.auth.get_session()
if session is not None:
    fresh.auth.set_session(session)

set_session() copies the session without making a request, persisting credentials, or notifying auth-state subscribers. It raises ValueError when the session type or any credential field is incomplete.

Password sign-in raises SessionChangedError if local session state changes while the request is in flight. The late response does not replace the newer state or emit a sign-in notification.

Refresh the current session

Refresh the session with its current refresh token:

refreshed = client.auth.refresh_session()
assert client.auth.get_session() is refreshed

On success, refresh_session() replaces the in-memory session and returns the immutable new snapshot. An authentication rejection from the refresh endpoint clears the captured session. Missing refresh credentials, failed session-continuity checks, server errors, and transport failures preserve it, and a late response never replaces a newer session. The SDK does not persist sessions.

Observe authentication changes

Observe local session transitions:

from volcano_sdk import AuthChangeEvent, Session


def handle_auth_change(event: AuthChangeEvent, session: Session | None) -> None:
    print(event, session is not None)


subscription = client.auth.on_auth_state_change(handle_auth_change)
# Later, stop receiving events.
subscription.unsubscribe()

Registration queues INITIAL_SESSION. It normally arrives before registration returns, but an existing notification dispatch may deliver it afterward. Successful session creation, refresh, and local clearing emit SIGNED_IN, TOKEN_REFRESHED, and SIGNED_OUT. Callbacks are delivered locally in transition order after the state lock is released. Ordinary callback Exception failures are isolated; exceptions such as KeyboardInterrupt propagate after the session transition has committed. Unsubscribing prevents queued and future delivery; a callback already selected for delivery may finish after unsubscribe() returns. The SDK does not broadcast between processes or persist sessions.

Sign out the current session

Sign out by revoking and clearing the current session:

client.auth.sign_out()
assert client.auth.get_session() is None

Sign-out uses the refresh token directly when the SDK received both credentials together from sign-in or a validated refresh. Supplied credentials use the access-token session when its JWT contains a readable UUID session_id; on HTTP 401, the SDK can refresh once and revoke that same session without adopting the renewed credentials. Without that identifier, sign-out uses the supplied refresh token, or only clears local state if no refresh token is available. Calling sign_out() without a session succeeds without a request. A revocation failure is raised after the captured local session is cleared. Sign-out waits for an already-running refresh and uses its validated credentials. Later refresh attempts raise SessionChangedError without a request. Concurrent sign-out calls share one result. A separate sign-in or adoption remains current.

On this page