diff --git a/sdks/spec/README.md b/sdks/spec/README.md index 3b029df45..223abd3c0 100644 --- a/sdks/spec/README.md +++ b/sdks/spec/README.md @@ -2,6 +2,8 @@ This folder contains the specification for Stack Auth's SDKs. +When writing this specification, try to write imperative pseudocode as much as possible (be explicit about what things are named, etc.). + ## Notation The spec files use the following notation: @@ -30,3 +32,31 @@ The languages should adapt: - **Parameter conventions**: Objects vs. kwargs, etc. - **Framework hooks**: Eg. for React, add `use*` equivalents to `get*`/`list*` methods - **Everything else, wherever it makes sense**: Every language is unique and the patterns will differ. If you have to decide between what's idiomatic in a language vs. what was done in the Stack Auth SDK for other languages, use the idiomatic pattern. + +## Implementation Notes + +### Object Construction + +When constructing SDK objects (User, Team, etc.) from API responses: +1. Map naming conventions to your language's naming convention +2. Objects should hold a reference to the SDK client for making API calls +3. Objects can be mutable or immutable based on language conventions +4. `update()` methods should update local properties after successful API call + +### Caching + +Normal functions should not cache. Some frameworks, like React, have hooks that require caching; for these, require explicit guidance. + +### Pagination + +Most `list*` methods support pagination: +- Request with `cursor` and `limit` query params +- Response includes `pagination: { next_cursor?: string }` +- `next_cursor` is null or absent when no more pages +- Default limit is typically 100 +- Note that not all backend APIs support pagination, and some just return all items at once. + +### Date/Time Formats + +- API uses milliseconds since epoch for timestamps (e.g., `signed_up_at_millis`) +- Convert to your language's native Date/DateTime type diff --git a/sdks/spec/src/_utilities.spec.md b/sdks/spec/src/_utilities.spec.md index 1c5d74b32..47152ab71 100644 --- a/sdks/spec/src/_utilities.spec.md +++ b/sdks/spec/src/_utilities.spec.md @@ -48,7 +48,11 @@ On 401 response with code="invalid_access_token": ### Token Refresh -Use OAuth2 refresh_token grant to get new access token: +Use OAuth2 refresh_token grant to get new access token. + +Concurrency: Token refresh must be serialized. Only one refresh request should be in-flight at a time. +If a refresh is already in progress, wait for it to complete rather than starting another. +Use a mutex/lock to ensure this (or, if preferred in that framework, some kind of asynchronous mechanism that doesn't block the main thread). POST /api/v1/auth/oauth/token Content-Type: application/x-www-form-urlencoded @@ -62,7 +66,8 @@ Body (form-encoded): Response on success: { access_token: string, refresh_token?: string, ... } -On error (e.g., refresh_token_error): clear tokens, user is signed out. +On success: store new access_token. If refresh_token is returned, store it too. +On error (e.g., refresh_token_error): clear all tokens, user is signed out. Use an OAuth library (e.g., oauth4webapi) for proper OAuth2 handling. @@ -140,6 +145,19 @@ Store access_token and refresh_token. The tokenStore constructor option determin Many functions also accept a tokenStore parameter to override storage for that call. +### TokenStoreInit Type + +TokenStoreInit is a union type representing the different ways to provide token storage: + +``` +TokenStoreInit = + | "cookie" // [JS-ONLY] Browser cookies + | "memory" // In-memory storage + | { accessToken: string, refreshToken: string } // Explicit tokens + | RequestLike // Extract from request headers + | null // No storage +``` + ### Token Store Types "cookie": [JS-ONLY] @@ -155,8 +173,19 @@ Many functions also accept a tokenStore parameter to override storage for that c Use explicit token values directly. For custom token management scenarios. +RequestLike object: + An object that conforms to whatever the requests look like in common backend frameworks. For example, in JavaScript, these often have the shape `{ headers: { get(name: string): string | null } }`, but in other languages this may drastically differ (and may not even be an interface and instead rather just be an abstract class, or not exist at all). + + This exists as a simplified way to support common backend frameworks in a more accessible way than the `{ accessToken: string, refreshToken: string }` one. + + Extract tokens from the x-stack-auth header: + 1. Get header value: headers.get("x-stack-auth") + 2. Parse as JSON: { accessToken: string, refreshToken: string } + 3. Use those tokens for authentication + null: - No token storage. SDK methods requiring authentication will fail. Most useful for backends, as you can still specify the token store per-request. + No token storage. SDK methods requiring authentication will fail. + Most useful for backends, as you can still specify the token store per-request. ### x-stack-auth Header Format @@ -188,4 +217,33 @@ Methods that can return this error: - signInWithPasskey - callOAuthCallback -The attempt_code is short-lived and single-use. +The attempt_code is short-lived (a few minutes) and single-use. + + +## JWT Access Token Claims + +The access token is a JWT with these claims: + +| Claim | Maps to | Type | +|-------|---------|------| +| sub | id | string | +| name | displayName | string or null | +| email | primaryEmail | string or null | +| email_verified | primaryEmailVerified | boolean | +| is_anonymous | isAnonymous | boolean | +| is_restricted | isRestricted | boolean | +| restricted_reason | restrictedReason | object or null | +| exp | expiresAt | number (Unix timestamp) | +| iat | issuedAt | number (Unix timestamp) | + +To decode: split by ".", base64url-decode the second segment, parse as JSON. + + +## Unknown Errors + +If an API returns an error code not listed in the spec: +1. Create a generic StackAuthApiError with the code and message +2. Log the unknown error for debugging +3. Treat it as a general API error + +This ensures forward compatibility when new error codes are added. diff --git a/sdks/spec/src/apps/client-app.spec.md b/sdks/spec/src/apps/client-app.spec.md index 0a187c91b..9a590851c 100644 --- a/sdks/spec/src/apps/client-app.spec.md +++ b/sdks/spec/src/apps/client-app.spec.md @@ -22,12 +22,24 @@ Optional: "cookie" is JS-only due to complexity. See _utilities.spec.md for details. urls: object - Override handler URLs. Defaults under "/handler": + Override handler URLs. Defaults: + home: "/" signIn: "/handler/sign-in" signUp: "/handler/sign-up" + signOut: "/handler/sign-out" afterSignIn: "/" afterSignUp: "/" - ... see apps/backend for full list + afterSignOut: "/" + emailVerification: "/handler/email-verification" + passwordReset: "/handler/password-reset" + forgotPassword: "/handler/forgot-password" + magicLinkCallback: "/handler/magic-link-callback" + oauthCallback: "/handler/oauth-callback" + accountSettings: "/handler/account-settings" + onboarding: "/handler/onboarding" + teamInvitation: "/handler/team-invitation" + mfa: "/handler/mfa" + error: "/handler/error" oauthScopesOnSignIn: object Additional OAuth scopes to request during sign-in for each provider. @@ -264,10 +276,11 @@ Response: credential_enabled: bool, magic_link_enabled: bool, passkey_enabled: bool, - oauth_providers: [{ id: string, type: string }], + oauth_providers: [{ id: string }], client_team_creation_enabled: bool, client_user_deletion_enabled: bool, - domains: [{ domain: string, handler_path: string }] + allow_user_api_keys: bool, + allow_team_api_keys: bool } } @@ -813,32 +826,48 @@ Does not error. ## Redirect Methods -All redirect methods take optional { replace?: bool, noRedirectBack?: bool }. +All redirect methods take optional options: -redirectToSignIn() - redirect to signIn URL -redirectToSignUp() - redirect to signUp URL -redirectToSignOut() - redirect to signOut URL -redirectToAfterSignIn() - redirect to afterSignIn URL -redirectToAfterSignUp() - redirect to afterSignUp URL -redirectToAfterSignOut() - redirect to afterSignOut URL -redirectToHome() - redirect to home URL -redirectToAccountSettings() - redirect to accountSettings URL -redirectToForgotPassword() - redirect to forgotPassword URL -redirectToPasswordReset() - redirect to passwordReset URL -redirectToEmailVerification() - redirect to emailVerification URL -redirectToOnboarding() - redirect to onboarding URL -redirectToError() - redirect to error URL -redirectToMfa() - redirect to mfa URL -redirectToTeamInvitation() - redirect to teamInvitation URL -redirectToOAuthCallback() - redirect to oauthCallback URL -redirectToMagicLinkCallback() - redirect to magicLinkCallback URL +Options: + replace: bool? - if true, replace current history entry instead of pushing + - Browser: use location.replace() instead of location.assign() + - Mobile: affects navigation stack behavior + noRedirectBack: bool? - if true, don't set after_auth_return_to param -Special behavior for signIn/signUp/onboarding: -- If URL has after_auth_return_to query param, preserve it -- Otherwise, set after_auth_return_to to current URL (for redirect after auth) +Methods: + redirectToSignIn() - redirect to signIn URL + redirectToSignUp() - redirect to signUp URL + redirectToSignOut() - redirect to signOut URL + redirectToAfterSignIn() - redirect to afterSignIn URL + redirectToAfterSignUp() - redirect to afterSignUp URL + redirectToAfterSignOut() - redirect to afterSignOut URL + redirectToHome() - redirect to home URL + redirectToAccountSettings() - redirect to accountSettings URL + redirectToForgotPassword() - redirect to forgotPassword URL + redirectToPasswordReset() - redirect to passwordReset URL + redirectToEmailVerification() - redirect to emailVerification URL + redirectToOnboarding() - redirect to onboarding URL + redirectToError() - redirect to error URL + redirectToMfa() - redirect to mfa URL + redirectToTeamInvitation() - redirect to teamInvitation URL + redirectToOAuthCallback() - redirect to oauthCallback URL + redirectToMagicLinkCallback() - redirect to magicLinkCallback URL -Special behavior for afterSignIn/afterSignUp: -- Check URL for after_auth_return_to query param and redirect there instead +Implementation: + +1. Get the target URL from the urls config +2. For signIn/signUp/onboarding (unless noRedirectBack=true): + - Check if current URL has after_auth_return_to query param + - If yes: preserve it in the target URL + - If no: set after_auth_return_to to current page URL +3. For afterSignIn/afterSignUp: + - Check current URL for after_auth_return_to query param + - If present: redirect to that URL instead of the default +4. Perform redirect based on redirectMethod config: + - "browser": window.location.assign() or .replace() + - "nextjs": Next.js redirect() function [JS-ONLY] + - "none": don't redirect (for headless/API use) + - Custom navigate function: call it with the URL All require browser or framework-specific redirect capability. Do not error. diff --git a/sdks/spec/src/apps/server-app.spec.md b/sdks/spec/src/apps/server-app.spec.md index 130cbc88b..2b6a57f9c 100644 --- a/sdks/spec/src/apps/server-app.spec.md +++ b/sdks/spec/src/apps/server-app.spec.md @@ -289,6 +289,12 @@ Response: total: number } +EmailDeliveryInfo: + delivered: number - emails successfully delivered + bounced: number - emails that bounced (hard or soft) + complained: number - emails marked as spam by recipients + total: number - total emails sent + Does not error. @@ -327,16 +333,28 @@ Arguments: Returns: DataVaultStore -DataVaultStore has methods: +The Data Vault is a simple key-value store for storing sensitive data server-side. +Each store is isolated and identified by its ID. + +DataVaultStore: + id: string - the store ID + get(key: string): Promise GET /api/v1/data-vault/stores/{storeId}/items/{key} [server-only] + Returns the value for the key, or null if not found. set(key: string, value: string): Promise PUT /api/v1/data-vault/stores/{storeId}/items/{key} [server-only] Body: { value: string } + Sets or updates the value for the key. delete(key: string): Promise DELETE /api/v1/data-vault/stores/{storeId}/items/{key} [server-only] + Deletes the key-value pair. No error if key doesn't exist. + + list(): Promise + GET /api/v1/data-vault/stores/{storeId}/items [server-only] + Returns all keys in the store. Does not error. diff --git a/sdks/spec/src/types/auth/oauth-connection.spec.md b/sdks/spec/src/types/auth/oauth-connection.spec.md index c7d55fc14..9ac79e9cf 100644 --- a/sdks/spec/src/types/auth/oauth-connection.spec.md +++ b/sdks/spec/src/types/auth/oauth-connection.spec.md @@ -69,12 +69,12 @@ options: { allowConnectedAccounts?: bool, } -Returns: Result +Returns: void PATCH /api/v1/users/me/oauth-providers/{id} { allow_sign_in, allow_connected_accounts } [authenticated] Route: apps/backend/src/app/api/latest/users/me/oauth-providers/[id]/route.ts -Errors (in Result): +Errors: OAuthProviderAccountIdAlreadyUsedForSignIn code: "oauth_provider_account_id_already_used_for_sign_in" message: "This OAuth account is already linked to another user for sign-in." @@ -111,12 +111,12 @@ options: { allowConnectedAccounts?: bool, } -Returns: Result +Returns: void PATCH /api/v1/users/{userId}/oauth-providers/{id} [server-only] Body: { account_id, email, allow_sign_in, allow_connected_accounts } -Errors (in Result): +Errors: OAuthProviderAccountIdAlreadyUsedForSignIn code: "oauth_provider_account_id_already_used_for_sign_in" message: "This OAuth account is already linked to another user for sign-in." diff --git a/sdks/spec/src/types/common/api-keys.spec.md b/sdks/spec/src/types/common/api-keys.spec.md new file mode 100644 index 000000000..d0cc8f619 --- /dev/null +++ b/sdks/spec/src/types/common/api-keys.spec.md @@ -0,0 +1,107 @@ +# ApiKey (Base) + +Base type for API keys. + + +## Properties + +id: string + Unique API key identifier. + +description: string + User-provided description of what this key is for. + +expiresAt: Date | null + When the key expires, or null if it never expires. + +createdAt: Date + When the key was created. + +isValid: bool + Whether the key is currently valid (not expired, not revoked). + + +## Methods + + +### revoke() + +DELETE /api/v1/api-keys/{id} [authenticated] + +Revokes the API key immediately. + +Does not error. + + +### update(options) + +options.description: string? +options.expiresAt: Date | null? + +PATCH /api/v1/api-keys/{id} { description, expires_at } [authenticated] + +Does not error. + + +--- + +# UserApiKey + +An API key owned by a user. + +Extends: ApiKey + + +## Additional Properties + +userId: string + The user who owns this key. + +teamId: string | null + If this key is scoped to a team, the team ID. + + +--- + +# UserApiKeyFirstView + +Returned only when creating a new API key. Contains the actual key value. + +Extends: UserApiKey + + +## Additional Properties + +apiKey: string + The actual API key value. Only returned once at creation time. + Store this securely - it cannot be retrieved again. + + +--- + +# TeamApiKey + +An API key owned by a team. + +Extends: ApiKey + + +## Additional Properties + +teamId: string + The team that owns this key. + + +--- + +# TeamApiKeyFirstView + +Returned only when creating a new team API key. + +Extends: TeamApiKey + + +## Additional Properties + +apiKey: string + The actual API key value. Only returned once at creation time. diff --git a/sdks/spec/src/types/common/sessions.spec.md b/sdks/spec/src/types/common/sessions.spec.md new file mode 100644 index 000000000..cdf4920d7 --- /dev/null +++ b/sdks/spec/src/types/common/sessions.spec.md @@ -0,0 +1,55 @@ +# ActiveSession + +Represents an active login session for a user. + + +## Properties + +id: string + Unique session identifier. + +userId: string + The user this session belongs to. + +createdAt: Date + When the session was created. + +isImpersonation: bool + Whether this is an impersonation session (admin viewing as user). + +lastUsedAt: Date | null + When the session was last used for an API request. + +isCurrentSession: bool + Whether this is the session making the current request. + +geoInfo: GeoInfo | null + Geographic information about where the session was last used. + + +--- + +# GeoInfo + +Geographic information derived from IP address. + + +## Properties + +city: string | null + City name, if detected. + +region: string | null + Region/state name, if detected. + +country: string | null + Country code (ISO 3166-1 alpha-2), if detected. + +countryName: string | null + Full country name, if detected. + +latitude: number | null + Approximate latitude. + +longitude: number | null + Approximate longitude. diff --git a/sdks/spec/src/types/notifications/notification-category.spec.md b/sdks/spec/src/types/notifications/notification-category.spec.md new file mode 100644 index 000000000..6c4a90de8 --- /dev/null +++ b/sdks/spec/src/types/notifications/notification-category.spec.md @@ -0,0 +1,42 @@ +# NotificationCategory + +A category of notifications that users can subscribe to or unsubscribe from. + + +## Properties + +id: string + Unique category identifier (e.g., "marketing", "product_updates", "security"). + +displayName: string + Human-readable name for the category. + +description: string | null + Description of what notifications this category includes. + +isSubscribedByDefault: bool + Whether users are subscribed to this category by default. + +isUserSubscribed: bool + Whether the current user is subscribed to this category. + + +## Methods + + +### subscribe() + +POST /api/v1/notification-preferences { category_id, subscribed: true } [authenticated] + +Subscribes the user to this notification category. + +Does not error. + + +### unsubscribe() + +POST /api/v1/notification-preferences { category_id, subscribed: false } [authenticated] + +Unsubscribes the user from this notification category. + +Does not error. diff --git a/sdks/spec/src/types/payments/customer.spec.md b/sdks/spec/src/types/payments/customer.spec.md index 64ff37669..3ceaedf60 100644 --- a/sdks/spec/src/types/payments/customer.spec.md +++ b/sdks/spec/src/types/payments/customer.spec.md @@ -208,6 +208,31 @@ displayName: string prices: Price[] +--- + +# Price + +A price point for a product. + + +## Properties + +id: string + Unique price identifier. + +amount: number + Price amount in the smallest currency unit (e.g., cents for USD). + +currency: string + Three-letter currency code (e.g., "usd", "eur"). + +interval: "month" | "year" | null + Billing interval for subscriptions, or null for one-time purchases. + +intervalCount: number | null + Number of intervals between billings (e.g., 1 for monthly, 3 for quarterly). + + --- # ServerItem (server-only) diff --git a/sdks/spec/src/types/projects/project.spec.md b/sdks/spec/src/types/projects/project.spec.md index 92ce9e69b..06d1feea1 100644 --- a/sdks/spec/src/types/projects/project.spec.md +++ b/sdks/spec/src/types/projects/project.spec.md @@ -38,10 +38,16 @@ passkeyEnabled: bool oauthProviders: OAuthProviderConfig[] List of enabled OAuth providers. - Each has: id: string, type: "google" | "github" | "microsoft" | etc. + Each has: id: string clientTeamCreationEnabled: bool Whether clients can create teams. clientUserDeletionEnabled: bool Whether clients can delete their own accounts. + +allowUserApiKeys: bool + Whether users can create API keys. + +allowTeamApiKeys: bool + Whether teams can create API keys. diff --git a/sdks/spec/src/types/teams/server-team.spec.md b/sdks/spec/src/types/teams/server-team.spec.md index f0caf67f3..1718cc709 100644 --- a/sdks/spec/src/types/teams/server-team.spec.md +++ b/sdks/spec/src/types/teams/server-team.spec.md @@ -41,9 +41,12 @@ Returns: ServerTeamUser[] GET /api/v1/teams/{teamId}/users [server-only] Route: apps/backend/src/app/api/latest/teams/[teamId]/users/route.ts -ServerTeamUser extends ServerUser with: +ServerTeamUser: + Extends ServerUser with: teamProfile: ServerTeamMemberProfile +See types/teams/team-member-profile.spec.md for ServerTeamMemberProfile. + Does not error. diff --git a/sdks/spec/src/types/teams/team-member-profile.spec.md b/sdks/spec/src/types/teams/team-member-profile.spec.md new file mode 100644 index 000000000..67ca9e101 --- /dev/null +++ b/sdks/spec/src/types/teams/team-member-profile.spec.md @@ -0,0 +1,66 @@ +# TeamMemberProfile + +A user's profile within a specific team. Teams can have per-user display names +and profile images that differ from the user's global profile. + + +## Properties + +displayName: string | null + The user's display name within this team. + +profileImageUrl: string | null + The user's profile image URL within this team. + + +--- + +# EditableTeamMemberProfile + +The current user's editable profile within a team. + +Extends: TeamMemberProfile + + +## Methods + + +### update(options) + +options.displayName: string | null? +options.profileImageUrl: string | null? + +PATCH /api/v1/teams/{teamId}/users/me/profile { display_name, profile_image_url } [authenticated] + +Updates the current user's profile within the team. + +Does not error. + + +--- + +# ServerTeamMemberProfile + +Server-side team member profile with additional management capabilities. + +Extends: TeamMemberProfile + + +## Additional Properties + +userId: string + The user ID this profile belongs to. + + +## Methods + + +### update(options) + +options.displayName: string | null? +options.profileImageUrl: string | null? + +PATCH /api/v1/teams/{teamId}/users/{userId}/profile [server-only] +Body: { display_name, profile_image_url } + +Does not error. diff --git a/sdks/spec/src/types/teams/team.spec.md b/sdks/spec/src/types/teams/team.spec.md index 9cb856bd9..68284955c 100644 --- a/sdks/spec/src/types/teams/team.spec.md +++ b/sdks/spec/src/types/teams/team.spec.md @@ -66,13 +66,11 @@ Returns: TeamUser[] GET /api/v1/teams/{teamId}/users [authenticated] Route: apps/backend/src/app/api/latest/teams/[teamId]/users/route.ts -TeamUser has: - id: string - teamProfile: TeamMemberProfile +TeamUser: + id: string - user ID + teamProfile: TeamMemberProfile - user's profile within this team -TeamMemberProfile has: - displayName: string | null - profileImageUrl: string | null +See types/teams/team-member-profile.spec.md for TeamMemberProfile. Does not error. @@ -83,11 +81,14 @@ Returns: TeamInvitation[] GET /api/v1/teams/{teamId}/invitations [authenticated] -TeamInvitation has: - id: string - recipientEmail: string | null - expiresAt: Date +TeamInvitation: + id: string - invitation ID + recipientEmail: string | null - email the invitation was sent to + expiresAt: Date - when the invitation expires + revoke(): Promise + DELETE /api/v1/teams/{teamId}/invitations/{id} [authenticated] + Revokes the invitation so it can no longer be accepted. Does not error. @@ -102,8 +103,8 @@ Returns: TeamApiKeyFirstView POST /api/v1/teams/{teamId}/api-keys { description, expires_at, scope } [authenticated] -TeamApiKeyFirstView extends TeamApiKey with: - apiKey: string - the actual key value (only shown once) +See types/common/api-keys.spec.md for TeamApiKeyFirstView. +The apiKey property is only returned once at creation time. Does not error. @@ -114,11 +115,7 @@ Returns: TeamApiKey[] GET /api/v1/teams/{teamId}/api-keys [authenticated] -TeamApiKey has: - id: string - description: string - expiresAt: Date | null - createdAt: Date +See types/common/api-keys.spec.md for TeamApiKey. Does not error. diff --git a/sdks/spec/src/types/users/current-user.spec.md b/sdks/spec/src/types/users/current-user.spec.md index d158d69de..86ca49a8d 100644 --- a/sdks/spec/src/types/users/current-user.spec.md +++ b/sdks/spec/src/types/users/current-user.spec.md @@ -175,10 +175,7 @@ Returns: EditableTeamMemberProfile GET /api/v1/teams/{teamId}/users/me/profile [authenticated] -EditableTeamMemberProfile has: - displayName: string | null - profileImageUrl: string | null - update(options): Promise +See types/teams/team-member-profile.spec.md for EditableTeamMemberProfile. Does not error. @@ -346,14 +343,7 @@ Returns: ActiveSession[] GET /api/v1/users/me/sessions [authenticated] -ActiveSession has: - id: string - userId: string - createdAt: Date - isImpersonation: bool - lastUsedAt: Date | null - isCurrentSession: bool - geoInfo: GeoInfo? +See types/common/sessions.spec.md for ActiveSession and GeoInfo. Does not error. @@ -402,6 +392,8 @@ Returns: UserApiKey[] GET /api/v1/users/me/api-keys [authenticated] +See types/common/api-keys.spec.md for UserApiKey. + Does not error. @@ -416,8 +408,8 @@ Returns: UserApiKeyFirstView POST /api/v1/users/me/api-keys { description, expires_at, scope, team_id } [authenticated] -UserApiKeyFirstView extends UserApiKey with: - apiKey: string - the actual key value (only shown once) +See types/common/api-keys.spec.md for UserApiKeyFirstView. +The apiKey property is only returned once at creation time. Does not error. @@ -431,22 +423,69 @@ Returns: NotificationCategory[] GET /api/v1/notification-categories [authenticated] +See types/notifications/notification-category.spec.md for NotificationCategory. + Does not error. -## Auth Methods (from StackClientApp) +## Auth Methods -signOut(options?) - Same as StackClientApp.signOut() +These methods are available on the CurrentUser object for convenience. +They operate on the user's current session. -getAccessToken() - Same as StackClientApp.getAccessToken() -getRefreshToken() - Same as StackClientApp.getRefreshToken() +### signOut(options?) -getAuthHeaders() - Same as StackClientApp.getAuthHeaders() +options.redirectUrl: string? - where to redirect after sign out + +Signs out the current user by invalidating their session. + +Implementation: +1. DELETE /api/v1/auth/sessions/current [authenticated] + (Ignore errors - session may already be invalid) +2. Clear stored tokens +3. Redirect to redirectUrl or afterSignOut URL + +Does not error. + + +### getAccessToken() + +Returns: string | null + +Returns the current access token, refreshing if needed. +Returns null if not authenticated. + +Does not error. + + +### getRefreshToken() + +Returns: string | null + +Returns the current refresh token. +Returns null if not authenticated. + +Does not error. + + +### getAuthHeaders() + +Returns: { "x-stack-auth": string } + +Returns headers for cross-origin authenticated requests. +The value is JSON: { "accessToken": "", "refreshToken": "" } + +Does not error. + + +### getAuthJson() + +Returns: { accessToken: string | null, refreshToken: string | null } + +Returns the current tokens as an object. + +Does not error. ## Deprecated Methods