Flares Cloud is not currently available to new customers. This documentation is kept for the projects already running on it, and stays accurate for them. What happened.

Flares Developer Docs

Sessions on a device

Sign-in, token storage, refresh under rotation, one-time codes and deep links — the whole session lifecycle on a phone.

Auth gives your application its own users — held in your project, separate from every other project, and nothing to do with how anybody signs in to Flares itself. This page is the session lifecycle from the device's point of view: getting a token, keeping it somewhere safe, and refreshing it without signing your user out by accident.

What a sign-in returns

POST /v1/projects/prj_…/auth/signin
Authorization: Bearer basket_public_…
Content-Type: application/json

{ "email": "aisha@example.com", "password": "a-strong-passphrase" }
{
  "user": { "id": "usr_…", "email": "aisha@example.com",
            "email_verified": false, "status": "active" },
  "access_token": "eyJ…",
  "expires_in": 3600,
  "refresh_token": "frt_…"
}
TokenLives forWhat it is for
access_token60 minutes by default Sent with every request. Short-lived on purpose: if it leaks, it expires.
refresh_token30 days by default Exchanged for a new access token. This is the one that keeps somebody signed in between app launches.

Both lifetimes are per project and can be changed in the console under Auth settings. A banking app and a game should not have the same session length.

Where to keep them

PlatformUseNot
iOSKeychain, with kSecAttrAccessibleAfterFirstUnlock UserDefaults — it is a plist, readable from a backup
AndroidEncryptedSharedPreferences, or the Keystore Plain SharedPreferences, or a file in app storage
Flutterflutter_secure_storage, which wraps both Anything that writes to the documents directory
React NativeKeychain / Keystore through a native module AsyncStorage — unencrypted on both platforms

Use after first unlock rather than when unlocked for the access item if your app does background work; otherwise a background refresh on a locked phone cannot read the token it needs.

Refresh tokens rotate. This is the part that catches people.

Every refresh returns a new refresh token and retires the one you used. If a retired token is presented again, Flares treats it as a replay — the assumption being that two parties now hold the same token, one of whom stole it — and revokes the entire session family. The user is signed out on that device.

POST /v1/projects/prj_…/auth/refresh
Authorization: Bearer basket_public_…

{ "refresh_token": "frt_…" }

→ { "access_token": "eyJ…", "expires_in": 3600, "refresh_token": "frt_NEW…" }
On a phone this is easy to trip over three different ways: two requests get a 401 at the same moment and both refresh; the process is killed after the response is sent but before you store the new token; a request times out on a bad connection and your retry layer sends it again. Each replays a retired token, and the user is signed out for no reason they can see.

The three rules that avoid it

  1. One refresh at a time. Serialise it. Every caller that hits a 401 waits on the same in-flight refresh rather than starting its own.
  2. Store the new token before you use it. Write the successor to secure storage as soon as the response arrives — before retrying whatever was waiting. A process killed after the write still has a usable token; one killed before it does not.
  3. Never retry a refresh. Retrying any other request is fine. Retrying a refresh is by definition sending a token that may already have been consumed. If a refresh fails, sign the user in again.

A serialised refresh

// Swift — one actor owns the token, so only one refresh can be in flight
actor SessionStore {
    private var refreshTask: Task<String, Error>?

    func validAccessToken() async throws -> String {
        if let token = keychain.accessToken, !isExpired(token) { return token }

        // Somebody is already refreshing: wait for that one.
        if let existing = refreshTask { return try await existing.value }

        let task = Task { () -> String in
            defer { refreshTask = nil }
            let fresh = try await callRefresh(keychain.refreshToken)

            // Persist BOTH tokens before anybody uses either.
            keychain.accessToken = fresh.accessToken
            keychain.refreshToken = fresh.refreshToken

            return fresh.accessToken
        }

        refreshTask = task
        return try await task.value
    }
}

The equivalent on Android is a Mutex around the same three steps, or an OkHttp Authenticator that synchronises on the token store. The shape matters more than the language: check, wait, refresh once, persist, then proceed.

Signing in without a password

Typing a passphrase on a phone keyboard is miserable, so Auth supports one-time codes by email. Enable it under Auth in the console, alongside or instead of email and password.

POST /v1/projects/prj_…/auth/otp/request     { "email": "aisha@example.com" }
POST /v1/projects/prj_…/auth/otp/redeem      { "email": "aisha@example.com", "code": "123456" }

Redeeming returns exactly what a password sign-in returns, so the rest of your session handling is unchanged. The request step answers the same way whether or not the address exists — do not build a screen that reveals the difference.

On iOS, mark the code field .textContentType(.oneTimeCode); on Android use autofillHints="smsOTPCode" for the equivalent. The code arrives by email rather than SMS, but the autofill affordance still helps.

Verification and password reset, as deep links

Verification and reset emails contain a link. By default that link points at a web page, which on a phone means your user leaves your app. You can point them back into it instead: set verification URL and reset URL in Auth settings, and Flares will build its links from those.

myapp://auth/verify?token=…
https://yourdomain.example/auth/reset?token=…      // universal / app link

A universal link (iOS) or app link (Android) is better than a custom scheme: it opens your app when installed and your website when not, so a person reading the email on a laptop is not stuck. The token is appended by Flares; your app hands it back to the reset or verify endpoint.

Sessions the person can see

Each sign-in is its own session, so a user on a phone and a laptop has two. Both can be ended — from your app by calling sign-out, or by an administrator in the console. Ending a session takes effect on the next request; an access token already in flight remains valid until it expires, which is at most its 60 minutes.

POST /v1/projects/prj_…/auth/signout        this session
POST /v1/projects/prj_…/auth/signout-all    every session this user has

Sign out everywhere is worth putting in your settings screen. It is what somebody reaches for when they lose a phone, and it is one call.

Handling the failures

What you getWhat it meansDo
401 on a normal requestThe access token expired Refresh once, then retry the request
401 on a refreshThe refresh token is retired, revoked or expired Clear stored tokens and show sign-in. Do not retry
403Signed in, but a rule refused this record A permissions problem, not a session problem. Do not sign the user out
Network timeoutThe phone moved between networks Retry the request — but never the refresh
Treating 403 as a session failure is a common and confusing bug: the user is signed out and, on signing back in, hits the same refusal. Only a failed refresh means the session is over.

Next