Auth  /  Connect your own sign-in

Connect your own sign-in

Onelo Auth is the default sign-in. An app that already signs its users in with Supabase, Clerk, Firebase or another OpenID Connect issuer connects that sign-in instead and keeps its users: nobody signs up again and nothing is imported. Your provider keeps signing people in, your app hands Onelo the provider's token, and Onelo answers with an ordinary Onelo session that every module reads.

What an own sign-in is, and when to choose it

Every app has one Sign-in Mode. It is either Onelo Auth, where Onelo signs your users in, or your app's own sign-in, where Onelo trusts the tokens of one issuer you name.

Your app todayChooseWhy
Has no sign-in yetOnelo AuthPassword, magic link and social sign-in with Google, GitHub and Apple work together on one app and are already configured. There is no provider to set up and none of the limits on this page apply.
Already signs users in through a provider or its own OpenID Connect issuerOwn sign-inUsers keep their accounts and their way of signing in. You add one setting and a few lines of code.
Signs users in with something that issues no OpenID Connect tokenOnelo Auth, or put an OpenID Connect issuer in frontOnelo can only verify a signed token from an issuer that publishes its keys. See Limits today.

Own sign-in is available on every Onelo plan, Free included. Users who arrive this way count toward your monthly active users like any other user.

What Onelo needs from your sign-in

How you sign people in is your business: password, social sign-in, a one-time code, anything. Onelo never sees that step. It receives one thing, a signed token from your issuer, and reads everything it needs from inside it. The e-mail travels inside that token, not beside it: there is no separate field in which your app tells Onelo who the user is.

The token carriesClaimWhat Onelo does with it
The issuerissMust equal the issuer saved for the app, exactly.
The user’s stable subjectsubTogether with the issuer it IS the user in Onelo. Required.
The audienceaudChecked when the app’s configuration names an audience: Firebase (the project ID) and a generic issuer. When authorized parties are saved, azp is checked the same way.
Issue time and expiryiat, expBoth required, at most one hour apart. An expired token is refused.
E-mail, optionalemailCopied to the Onelo user. Without it the user gets a placeholder address.
Whether the e-mail is verified, optionalemail_verifiedOnly the value true marks the address verified.
Whether the user is anonymous, optionalis_anonymousRead only with the e-mail confirmation setting: an anonymous user’s address never counts as verified.

Three conditions make a sign-in connectable, whatever it is built with:

  1. One issuer. Every way of signing in ends in a token from the same issuer. An app trusts exactly one.
  2. Published public keys. The token is signed with an asymmetric key (RS256 or ES256), and Onelo can fetch the matching public key from the issuer's discovery document.
  3. The same subject for the same person. Somebody who signs in with a password one day and through a social provider the next must get the same sub. With two different subjects Onelo sees two people, and when both tokens carry the same e-mail it refuses the second with email_in_use.

Clerk and Firebase meet the first two as they come. Supabase meets the first, and the second once the project signs with an asymmetric key (see its section below). The third depends on how accounts are linked at your provider, so check that there. A home-built sign-in has to take care of all three itself.

How own sign-in works

  • Your app sends the provider's token to Onelo. Onelo checks its signature against the keys the issuer publishes, checks that it was issued for your project, and answers with an Onelo session.
  • A user is identified by the issuer and the user id inside the token (its sub), never by e-mail. The first exchange creates the user in Onelo; later ones find the same user.
  • The Onelo session lasts 30 minutes and has no refresh token. After about 80% of that time the SDK asks your function for a fresh provider token and exchanges again, so the user never sees it lapse.
  • A user removed at your provider gets no new token, so their Onelo session ends within 30 minutes. A ban in Onelo applies at once.
  • Onelo never receives your provider's secrets. Setup takes public values only, and the SDK sends only the user's token.

Turn on own sign-in: dashboard and MCP

In the dashboard. The setting is the Sign-in Mode section on the app's Auth screen.

WhereDashboard→your app→Auth→Sign-in Mode

Choose “My app's own sign-in”, pick the provider, fill in its public value and save.

When you save, Onelo fetches the issuer's OpenID Connect discovery document and its signing keys. The save is refused when the issuer does not answer, answers for a different issuer, or publishes no RS256 or ES256 signing key. Nothing is stored until that check passes.

ProviderWhat Onelo asks forExample
SupabaseProject reference or project URLhttps://abcdefghijklmnopqrst.supabase.co
ClerkFrontend API URL; optionally the authorized parties (the origins allowed in azp)https://your-app.clerk.accounts.dev
FirebaseProject IDmy-app-1a2b3
Other OpenID Connect issuerIssuer URL and audience (the token’s aud claim); optionally the authorized partieshttps://auth.example.com
The Sign-in Mode can change only while the app has no users. Once somebody has signed in, the setting locks, because switching it would leave those users without a way in. A save is then refused with sign_in_mode_locked. To move the users, press Ask Onelo to migrate my users on the locked Sign-in Mode card (Dashboard → your app → Auth): Onelo receives the request and gets in touch to plan the move. Choose the mode before you launch. The one exception is Onelo's own staging environment, where the switch stays open for testing: users there are not moved and cannot sign in until the app is switched back.

A platform app of an Umbrella app has no Sign-in Mode of its own. Its users sign in at the Umbrella app, so set the mode there.

Through the MCP. An AI agent connected to the Onelo MCP server does the same thing in two calls.

  • auth.get_guide leads the setup. When it asks whether the app takes Onelo Auth, the answer for an app with its own sign-in is existing. The guide then asks for the provider and says where each value is.
  • auth.set_sign_in_mode saves it: mode is external, preset is supabase, clerk, firebase or oidc, followed by that provider's public values (project, issuer, project_id, audience, authorized_parties).
  • The change always waits for your approval in the Onelo panel. The agent's first call comes back with confirmation_required and an approval id; after you approve, the agent repeats the call with that id.

Only public values are needed, read from your app's code or config. Never give Onelo or an agent a provider secret or service key: nothing here uses one.

Supabase: step by step

  1. Find the project URL your app already passes to the Supabase client, for example https://abcdefghijklmnopqrst.supabase.co. The 20-character project reference alone works too, and so does a custom domain.
  2. In the Supabase dashboard open the JWT signing keys settings and make sure the project signs with an asymmetric key. A project still on the legacy shared secret uses “Migrate JWT secret” and then “Rotate keys”.
  3. Save the Sign-in Mode in Onelo with the Supabase provider and that project value.
  4. Add the Supabase block below to your app.
What trips people up: Supabase needs asymmetric signing keys. Onelo accepts ES256 and RS256 only. Tokens signed with the legacy shared secret (HS256) cannot be verified without that secret, so the save is refused. Supabase also offers Ed25519 keys; Onelo does not accept those either, so pick ES256 or RS256.
typescript — Supabase
// `supabase` is your existing Supabase client, `onelo` the instance above.
// The project must sign its tokens with an asymmetric key (ES256 or RS256);
// the legacy shared-secret (HS256) tokens are refused.

// getSession() refreshes the Supabase session when needed. null = signed out.
const supabaseToken = async () =>
  (await supabase.auth.getSession()).data.session?.access_token ?? null

// Fires once on start (INITIAL_SESSION), then on every sign-in and sign-out.
let supabaseUser: string | null | undefined
supabase.auth.onAuthStateChange((_event, session) => {
  const user = session?.user.id ?? null
  if (user === supabaseUser) return // a token refresh, not another user
  supabaseUser = user
  // Outside the callback: Supabase calls must not run inside it.
  setTimeout(() => {
    onelo.auth.useExternalToken(supabaseToken).catch((e) => console.error('[Onelo]', e))
  }, 0)
})

Clerk: step by step

  1. Find your instance's Frontend API URL. It looks like https://your-app.clerk.accounts.dev in development and https://clerk.your-domain.com in production. It is the iss of every Clerk session token.
  2. In the Clerk Dashboard open the Sessions page. Under “Customize session token”, add email and email_verified in the Claims editor and save.
  3. Save the Sign-in Mode in Onelo with the Clerk provider and the Frontend API URL. Optionally list the authorized parties: the origins of your app, such as https://app.example.com. When the list is set, a token whose azp is not on it is refused.
  4. Add the Clerk block below to your app.
What trips people up: Clerk's default session token carries no e-mail. Without the two claims from step 2, Onelo stores a placeholder address for each user instead of a real one. Development and production instances of Clerk have different Frontend API URLs, and an app trusts one of them.
typescript — Clerk
// `clerk` is your loaded Clerk instance (window.Clerk, or useClerk() in
// React), `onelo` the instance above.

// getToken() answers from Clerk's cache and fetches a new token when the
// one-minute token has expired. null = signed out.
const clerkToken = async () => (await clerk.session?.getToken()) ?? null

// Fires once on start, then whenever the Clerk session changes.
let clerkUser: string | null | undefined
clerk.addListener(({ session }) => {
  if (session === undefined) return // Clerk is still loading
  const user = session?.user?.id ?? null
  if (user === clerkUser) return
  clerkUser = user
  onelo.auth.useExternalToken(clerkToken).catch((e) => console.error('[Onelo]', e))
})

// Clerk's session token carries no e-mail by default. To give Onelo the
// address, add `email` and `email_verified` to the session token in the Clerk
// dashboard (Sessions → Customize session token).

Firebase: step by step

  1. Find the Firebase project ID: Project settings → General in the Firebase console, or projectId in your app's Firebase config. It looks like my-app-1a2b3.
  2. Save the Sign-in Mode in Onelo with the Firebase provider and that project ID.
  3. Add the Firebase block below to your app.
What trips people up: a Firebase project ID is not checked when you save. Google answers the discovery request for any project ID, even one that does not exist, so Onelo cannot tell a real project from a typo. A mistyped ID saves without an error, and every sign-in is then refused. Copy it exactly from the Firebase console.
typescript — Firebase
import { getAuth, onAuthStateChanged } from 'firebase/auth'

// `onelo` is the instance above.
const firebaseAuth = getAuth()

// getIdToken() returns the current ID token and refreshes one that expires
// within five minutes. No current user = signed out.
const firebaseToken = async () => (await firebaseAuth.currentUser?.getIdToken()) ?? null

// Fires once on start, then on every sign-in and sign-out.
onAuthStateChanged(firebaseAuth, () => {
  onelo.auth.useExternalToken(firebaseToken).catch((e) => console.error('[Onelo]', e))
})

Another OpenID Connect issuer

Any other issuer connects through the same door, with the issuer URL and the audience. What it has to do:

  • Publish a discovery document at <issuer>/.well-known/openid-configuration whose issuer equals the URL you enter, character for character, and which names a key set (jwks_uri).
  • Sign with RS256 or ES256, and give every signing key a key id (kid).
  • Put in each token: iss equal to the issuer, aud containing the audience you entered, a non-empty sub, and both iat and exp, at most one hour apart.
  • Be reachable over HTTPS on a public address, on the default port, without redirects.

To give Onelo the user's address, include the standard email and email_verified claims. There is no ready-made block for a generic issuer: hand useExternalToken a function that returns your issuer's current token, the way the provider blocks above do.

Wire your app: web (JavaScript) and Node.js

Install and initialize the JavaScript SDK in the browser and, when you have a server, the Node.js SDK there. These are the same install panels as on the Auth page.

① Clientfrontend

Install

npm install github:onelo-tools/onelo-js

Initialize

import { Onelo } from '@onelo/js'

// Initialize once (e.g. in a module-level singleton)
const onelo = new Onelo({
  publishableKey: 'onelo_pk_live_YOUR_KEY',
  apiUrl: 'https://api.onelo.tools',
})

Usage

// Sign in. This is your app's FIRST screen for a visitor with no session:
// call it where you would otherwise redirect to /login — NOT from a "Sign in"
// button. It renders INSIDE your own window (no popup, no extra step) and
// auto-routes to waitlist / paywall-store when those are enabled for your app.
//
// Once the app is live, your site's origin must be in Allowed Domains
// (dashboard → app → Security). While the app is not live nothing is enforced,
// whatever that list holds — localhost and a staging host just work. After
// "Go live" only origins on the list work, exact match: add every one you serve
// from (www, staging subdomain) before you switch.
const session = await onelo.loadAuthView()

// Escape hatch — a top-level popup window instead of the in-page view. Only for
// origins you genuinely cannot add (a preview host with a rotating hostname, an
// embedder whose CSP you don't control). Browsers can block popups, so never
// reach for this just to skip the Allowed Domains step.
//    const session = await onelo.loadAuthView({ mode: 'popup' })

// Read the current session + react to changes. null = signed out: your own
// signOut(), a remote revoke, a declined consent gate, or a session that ENDED
// (expired, or its refresh token was replaced — e.g. another device). None of
// these throws — getSession() just answers null. A gated app goes straight back
// to sign-in; this ONE handler covers every case:
const current = await onelo.auth.getSession()
const unsub = onelo.auth.onAuthStateChange((s) => {
  if (!s) void onelo.loadAuthView()
})

// ── Paywall hard-gate (apps that sell access) ───────────────
// loadAuthView() (above) never lets an unentitled user through: it resolves with
// a session ONLY once they are allowed in, and null otherwise. Which screen it
// shows them first depends on your Access Gate — the store, or Onelo's "No
// active plan" — but the RESULT is the same either way, which is exactly why you
// gate on the result and never on "sign-in happened". (Unlike Electron you don't
// manage a window — your framework re-renders on state — but this is the same
// entitlement the SDK enforces server-side on every call.)
// ONE call: "signed in AND allowed in". It folds the session check and the
// entitlement check together, so there is nothing to assemble by hand — and a
// gate you assemble by hand is a gate you can assemble wrongly. (The composed
// form this replaces omitted the session check, so a signed-out visitor to a
// NON-paywalled app passed it.) Requires @onelo/js 0.42.0+.
if (await onelo.auth.isAllowedIn()) {
  // → render your app
} else {
  // Not entitled. WHAT the user was just shown depends on your Access Gate
  // (Paywall → Store → Access Gate), so do not assume a store:
  //   "Show the store" — they saw the store and closed it without buying.
  //   "Sign in only"   — there is no in-app store at all; they saw Onelo's
  //                      "No active plan" screen. Sending them to a store here
  //                      would point at something that does not exist.
  // Either way: keep your locked screen. loadAuthView() re-opens whichever
  // surface is correct for your settings, so call that rather than a store.
}

// Sign out (also revokes the session server-side).
//
// This clears the session and fires onAuthStateChange(null) — and that is ALL
// it does. It does NOT open the sign-in screen for you, because whether a
// signed-out visitor should see a login box, a marketing page or a guest mode
// is your app's decision, not ours. In a gated app the onAuthStateChange
// handler above re-opens sign-in — for this AND for a session that ended.
await onelo.auth.signOut()

// ── Magic link ──────────────────────────────────────────────
// Nothing to wire when you use loadAuthView(): the hosted page offers the link,
// and the tab opened from the email hands the sign-in back to the tab you
// started in, which then continues as normal. The email tab shows a short
// "you can close this" message instead of a second copy of your app.
//
// Building your own sign-in UI instead? sendMagicLink needs a return URL on the
// web — the email always opens a NEW tab, so without one there is nowhere to
// come back to. It must be an absolute URL on an origin in your Allowed Domains
// (a bare path is refused); the SDK finishes the sign-in when the browser lands
// back there. This one check does not wait for "Go live": as soon as the list
// has an entry, the return URL has to be on it.
//   await onelo.auth.sendMagicLink(email, "https://yourapp.com/signed-in")

// ── Legal-consent gate (Terms / Privacy updates) ────────────
// When you publish a version with "Require in-app acceptance" ON in the
// dashboard (works for BOTH Terms of Service AND Privacy Policy), signed-in
// users get a BLOCKING full-screen gate they must accept — or Sign out — before
// continuing. Auto-presents on sign-in and the moment you publish (over SSE).
// "Notify only" versions never block. Nothing to wire — it's on by default.
//
// Return to sign-in on ANY sign-out (the gate's Sign out, a session revoke, or
// an ended session): the onAuthStateChange handler above already does it.
//
// Custom UI instead of the built-in gate (new Onelo({ autoPresentConsentGate: false })):
//   onelo.consent.onConsentRequired(() => { /* re-read + render your own banner */ })
//   const pending = await onelo.consent.requiredConsents()
//   await onelo.consent.acceptConsent(pending[0].versionId)
② Serverbackend

Install

npm install github:onelo-tools/onelo-node

Initialize

import { Onelo } from '@onelo/node'
import { requireUser } from '@onelo/node/express'

// Authenticate with your SERVER SECRET key (onelo_sk_live_…) — NOT a
// publishable key. Read it from the environment; never hard-code a secret.
const onelo = new Onelo({
  secretKey: process.env.ONELO_SECRET_KEY!,   // onelo_sk_live_…  (NEVER commit)
  apiUrl: 'https://api.onelo.tools',
})

Usage

// The frontend sends the user's ACCESS TOKEN (returned by the Onelo SDK after
// sign-in — NOT your secret key) as: Authorization: Bearer <token>.
// requireUser verifies it and attaches req.oneloUser. When the App's Sign-in
// Mode is an external provider (Supabase, Clerk, Firebase…), requireUser also
// accepts that provider's token, so the frontend keeps sending what it sends.
app.get('/me', requireUser(onelo), (req, res) => {
  // requireUser guarantees req.oneloUser is set here — assert non-null.
  res.json({ id: req.oneloUser!.id, email: req.oneloUser!.email })
})

// Post-verify gates + SSE support (all optional):
//   requireUser(onelo, { acceptQueryToken: true })   // allow ?token= for EventSource/SSE
//   requireUser(onelo, { identifyMonitor: false })   // opt out of auto monitor.setUser (on by default when a monitor scope is active)
//   optionalUser(onelo)   // sets req.oneloUser = null instead of 401 (a 503 outage still surfaces)

// ── Other frameworks — one adapter import, same onelo client ──
//   Next.js  → import { withUser } from '@onelo/node/next'
//              export const GET = withUser(onelo, async (req, user) =>
//                Response.json({ id: user.id }))
//   Fastify  → import { requireUser } from '@onelo/node/fastify'
//              fastify.get('/me', { preHandler: requireUser(onelo) },
//                async (req) => ({ id: req.oneloUser!.id }))
//   Hono     → import { requireUser } from '@onelo/node/hono'
//              app.use('/me', requireUser(onelo))
//              app.get('/me', (c) => c.json({ id: c.get('oneloUser').id }))
//   Any other (Koa, Nest, raw http, WebSocket, queue worker):
//              import { verifyToken } from '@onelo/node'
//              const user = await verifyToken(onelo, token)   // throws typed errors
//
// Error → status: OneloAuthInvalidToken / missing → 401 · OneloAuthForbidden /
//   failed gate → 403 · OneloAuthRateLimited (429) / OneloAuthUnavailable (5xx) → 503.

In the browser, add the block for your provider from the sections above. Each one hands onelo.auth.useExternalToken(getToken) a function that returns the provider's current token, or nothing when no user is signed in there. Then read the Onelo session as usual:

typescript — then, with any provider
// From here every module works as with Onelo Auth.
const session = await onelo.auth.getSession() // null = signed out at the provider
onelo.auth.onAuthStateChange((s) => {
  console.log(s ? `signed in as ${s.user.email}` : 'signed out')
})
if (await onelo.auth.isAllowedIn()) showApp()
  • When your function returns no token, the Onelo session ends and onAuthStateChange reports signed out.
  • Onelo sign-in screens are never shown in this mode: loadAuthView() only returns the current session.
  • On the server, requireUser and verifyToken from the Node.js SDK accept the provider's token as well as an Onelo session token. Your frontend keeps sending the Authorization: Bearer header it sends today; the SDK exchanges the token at Onelo with your secret key and hands you the Onelo user.

A server without tokens: the server assertion

If your backend signs users in with its own server sessions and has no issuer token to hand over, your server can say who the user is instead. Create an identity key on the API Keys page (Identity keys). It can only assert which user is signed in, can be limited to your server's IP addresses, and is not a Secret Key. Two keys can be active at once, so you can rotate without downtime. The Node.js SDK refuses to run this call in a browser.

typescript — Node.js server
import { assertIdentity } from '@onelo/node'

// On YOUR server, after your own sign-in has identified the user.
// The identity key (onelo_ik_live_…) comes from the dashboard: API Keys →
// Identity keys. It can only say who is signed in; it is not a Secret Key.
// Read it from the environment; never send it to a browser.
const { user, session } = await assertIdentity({
  apiUrl: 'https://api.onelo.tools',
  identityKey: process.env.ONELO_IDENTITY_KEY!,
  externalId: account.id,          // your own id for this user
  email: account.email,            // optional
  emailVerified: account.emailVerified === true,
})

// session.accessToken is an ordinary Onelo session (30 minutes, no refresh):
// hand it to your frontend, or use user.id with the other Onelo modules.
// Refusals throw typed errors with .code — invalid_identity_key,
// ip_not_allowed, sign_in_mode_onelo, banned, rate_limited …

What changes for your app

  • Onelo's own sign-in is closed for the app. The hosted sign-in and sign-up pages, password sign-in and password reset, magic link, Onelo's social sign-in with Google, GitHub and Apple, and account creation in the hosted store all answer sign_in_mode_external. Users come in through your sign-in only.
  • Every other module works with the same user. The exchange returns an ordinary Onelo session, and Paywall, Features, Consent and the rest read that session the way they read one from Onelo Auth.
  • Bans and plan limits apply unchanged. A banned user is refused at the exchange, and the monthly active user limit of your plan counts these users.

Limits today

  • Web (JavaScript) and Node.js only. Electron, Python and the mobile SDKs (Swift, Android, Flutter, React Native) still use Onelo Auth and cannot connect an own sign-in yet.
  • Social sign-in through your provider works unchanged. When Google, Apple or GitHub sign-in runs through the provider you connected, Onelo receives that provider's token, the same as after a password sign-in. There is nothing extra to configure.
  • One app trusts one issuer. If you wired several social providers directly, with no provider in between, you have several issuers (Google and Apple are two), and an app accepts one. The way through is your own backend issuing one OpenID Connect token after any of them.
  • The issuer must speak OpenID Connect with asymmetric keys. A sign-in that issues no OpenID Connect token, a home-made sign-in on session cookies, or tokens signed with a shared secret (HS256) cannot be connected: saving the Sign-in Mode is refused. What you need is a published discovery document and RS256 or ES256 signing keys.
  • Onelo Auth has none of these limits. Password, magic link and Google, GitHub and Apple sign-in work together on one app, already configured. That is the reason to choose Onelo Auth when the app has no provider yet.
  • Onelo's social sign-in is closed in this mode, together with the rest of Onelo Auth. The two cannot be mixed on one app.
  • Users are not imported or moved. Switching an app that already has users is locked, as described above.

E-mail rules for these apps

  • The address is copied from the token's email claim on every exchange, so a change at your provider reaches Onelo at the next sign-in or renewal.
  • It counts as verified only when the token says email_verified: true. Bulk mail from Onelo goes only to addresses your provider marked verified.
  • A token with no e-mail creates the user with a placeholder address that is not a real mailbox.
  • Accounts are never linked by e-mail. Two identities with the same address are two different people to Onelo, and the second one is refused (see email_in_use below).

Troubleshooting: refusal codes

Every refusal carries a code. In the browser the SDK's error message names it; in Node.js it is on the error's code property. When a save is refused, the MCP answers with the code and the message; the dashboard shows the message.

When saving the Sign-in Mode

CodeWhat it means
sign_in_mode_lockedThe app already has users, so the mode can no longer be changed here. Press Ask Onelo to migrate my users on the app’s Auth page in the dashboard.
parent_owned_settingThis is a platform app of an Umbrella app. Set the Sign-in Mode on the Umbrella app.
invalid_presetThe provider is not one of the four presets listed under “Turn on own sign-in”.
invalid_projectThe project value is neither a 20-character project reference nor a project URL starting with https://.
invalid_project_idFirebase: the value does not look like a project ID.
invalid_issuerClerk or a generic issuer: the issuer URL is missing.
invalid_audienceA generic issuer needs the audience its tokens carry in aud.
invalid_authorized_partiesAuthorized parties must be a list of at most 10 origins such as https://app.example.com.
issuer_not_httpsThe issuer must start with https://.
issuer_invalid_urlThe issuer is not a usable URL: it is malformed, or carries a user name, password or fragment.
issuer_port_not_allowedThe issuer must not name a port.
issuer_query_not_allowedThe issuer must not carry a query string.
issuer_private_addressThe issuer resolves to a private or internal address. It has to be reachable from the public internet.
issuer_unreachableOnelo could not reach the issuer, its host was not found, or it answered with an error status. Check the URL for a typo.
issuer_redirectThe issuer answered with a redirect. Onelo does not follow redirects; enter the final address.
issuer_timeoutThe issuer took too long to answer.
issuer_response_too_largeThe discovery document or key set is larger than 256 KB.
issuer_invalid_documentThe discovery document or key set is not valid JSON, or names no key set.
issuer_mismatchThe discovery document names a different issuer. Enter the issuer exactly as it appears there, including or excluding the trailing slash.
issuer_symmetric_keyThe issuer publishes a symmetric or private key. Only asymmetric signing keys are accepted.
issuer_no_signing_keysThe issuer publishes no RS256 or ES256 key. If it signs with a shared secret (HS256), switch it to asymmetric signing keys first.
issuer_key_without_kidA signing key of the issuer has no key id (kid).

When a user signs in

CodeWhat it means
sign_in_mode_oneloThe app is on Onelo Auth, so it does not accept a provider’s token. Set the Sign-in Mode before calling useExternalToken().
sign_in_mode_externalThe app uses its own sign-in, so Onelo Auth is not available for it. Something is still calling an Onelo sign-in screen or endpoint.
token_malformedWhat was sent is not a signed JWT. Check that your function returns the provider’s token and not a user object or an opaque session id.
token_alg_not_allowedThe token is signed with something other than RS256 or ES256, typically HS256 from a shared secret.
token_kid_missingThe token’s header names no signing key (kid).
token_key_unknownThe token is signed with a key the issuer does not publish. Often a token from another project or instance.
token_signature_invalidThe signature does not match the issuer’s key.
token_issuer_mismatchThe token comes from a different issuer than the one saved: another project, a development instance instead of production, or a mistyped Firebase project ID.
token_audience_mismatchThe token was issued for a different project or audience.
token_authorized_party_mismatchThe token’s azp is not one of the authorized parties saved for the app. Add the origin your app runs on.
token_expiredThe token has expired. The JavaScript SDK asks your function for a fresh one and retries.
token_not_yet_validThe token is dated in the future, usually a clock a little off. The JavaScript SDK retries.
token_lifetime_too_longThe token is valid for more than one hour, or lacks iat or exp. Shorten the token lifetime at the issuer.
token_subject_missingThe token names no user (sub).
email_in_useAnother user of this app already has this e-mail. See below.
bannedThe user is banned in Onelo.
account_deletedThe user’s Onelo account was deleted.
issuer_unavailableOnelo could not read the issuer’s signing keys just now. Temporary: the SDK keeps the session and retries.
rate_limitedToo many exchanges in a minute from one address, for one user or for the app. Temporary.
new_user_cap_reachedThe app created too many new users in the last hour. Temporary: existing users are not affected.
go_live_requiredThe app is still a draft and has reached its draft user limit, so no new user can be created. Go live to add more users.
plan_limit_exceededThe monthly active user limit of your Onelo plan is reached.
no_plan_availableThe app sells access and signs out users whose access has ended, and this user has no active plan.
email_in_use, in plain words. Onelo refused a sign-in because the token carries an e-mail that a different user of the same app already has. This happens when your provider holds two separate accounts with one e-mail, for example one created with a password and another created through a social sign-in that was never linked to it. Onelo never merges accounts by e-mail, because an e-mail alone does not prove the two are the same person. The remedy is on the provider's side: link the two accounts there so both ways of signing in lead to one account, or remove the duplicate.
Connect your own sign-in — Auth — Onelo Docs