FurniOpsDocs

OAuth apps

Register an OAuth app for your organisation, sign members in with the authorization code flow and PKCE, call the API as the organisation with client credentials, and handle scopes, consent, token lifetimes, refresh rotation and secret rotation.

FurniOps sign-in is an OAuth 2.1 and OpenID Connect server at https://auth.furniops.com. Your organisation can register its own OAuth apps on it:

  • an internal tool or a partner's integration that your members sign in to, acting within each member's role;
  • a server that calls the API as your organisation, with a client secret it can rotate instead of an API key.

Kinds

An app's kind decides how it authenticates and which flow it uses. It never changes.

KindForClient secretFlow
Web appA server-side app that signs users inYesAuthorization code with PKCE
Single-page appRuns in the browser and signs users inNo (public)Authorization code with PKCE
Native appA desktop or mobile app that signs users inNo (public)Authorization code with PKCE
MachineYour server calls the API as the organisation; no userYesClient credentials

PKCE (S256) is required for every kind that signs users in, with or without a secret. There is no implicit grant and no password grant.

Register an app

Under Developers › OAuth apps, choose Create an OAuth app. Owners, admins and developers can manage apps (oauth_apps:write); like API keys, every change needs a sign-in within the last 10 minutes.

  1. Name and kind.
  2. Scopes (fixed for life; make a new app to change them):
    • About the user (the kinds that sign users in): openid, profile, email and offline_access.
    • API permissions: what the app may do for the signed-in user, within what that user may do; for a machine app, what your server may do as the organisation (at least one). The choice is limited to the permissions an API key can hold that your own role holds.
  3. Redirect URIs (the kinds that sign users in): matched exactly. https, or http on a loopback host for local development; a native app may also use a private-use scheme in reverse-domain form (com.example.app:/callback).
  4. Sign-out redirect URIs (optional), and the links users see on the consent screen: privacy policy, terms of service, homepage and logo. An app that signs users in with openid must link its privacy policy.

The client ID and, for web and machine apps, a client secret are shown. The secret is shown once. An organisation can hold up to 25 apps.

Afterwards you can edit an app's name, redirect URIs and links, rotate or revoke its secrets, and delete it. Deleting an app stops its client ID and secrets at once and ends every user's sign-in and consent for it.

Sign users in: authorization code with PKCE

Discovery documents are at https://auth.furniops.com/.well-known/openid-configuration and /.well-known/oauth-authorization-server. The endpoints:

EndpointPath
Authorization/oauth/authorize
Token/oauth/token
Pushed authorization requests/oauth/par
Revocation/oauth/revoke
Introspection/oauth/introspect
User info/oauth/userinfo
Keys/oauth/jwks
Sign-out/oauth/end-session

Send the user to the authorization endpoint with a PKCE challenge, a state, and the API as the resource the token is for:

https://auth.furniops.com/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=openid%20email%20products%3Aread%20events%3Aread
  &resource=https%3A%2F%2Fapi.furniops.com
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
  &state=RANDOM_STATE

The user signs in to FurniOps (with their password, passkey, second factor or their organisation's single sign-on) and is asked to consent. Your app is redirected back with a code, valid for one minute, which you exchange at the token endpoint:

curl https://auth.furniops.com/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier="$VERIFIER"

A public app (single-page, native) sends client_id in the body instead of authenticating. The answer carries an access_token for the API and, with openid, an id_token. Call the API with the access token exactly as with a key:

curl https://api.furniops.com/v1/products \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Treat the access token as an opaque string.

An organisation's own app is never trusted by default: every user is asked to consent, on a screen that shows the app's name, logo, links and each permission in plain words. A user can remove an app's access later in their account's Connected apps, which ends its tokens in every organisation at once. A password reset also ends every app's access.

The app acts only in your organisation, and its tokens hold the scopes the user consented to intersected with that user's role, read again at every token: a member whose role changes, or who is removed, gets the new answer within seconds.

Machine apps: client credentials

A machine app gets a token for the organisation with no user:

curl https://auth.furniops.com/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource=https://api.furniops.com \
  -d scope="products:read events:read"

Name the API as the resource (or ask for API scopes): a client-credentials request that names no resource is refused. The token holds the app's scopes and lasts 10 minutes; ask for a new one when it expires. There is no refresh token.

Token lifetimes

ArtifactLifetime
Access token (users and machine apps)10 minutes
ID token1 hour
Authorization code1 minute, used once
Sign-in and consent screens10 minutes
Refresh token7 days idle, at most 30 days from the first one of its family

Refresh tokens

Apps that sign users in get a refresh token when the app was registered with offline_access and the authorization request asks for it with prompt=consent (OpenID Connect keeps offline_access only when consent is asked for). Such a token outlives the user signing out of FurniOps, until it expires, the user removes the app, or their password is reset.

  • Every refresh rotates. The answer carries a new refresh token; the old one is spent.
  • Reuse revokes the family. Presenting a spent refresh token ends every token descended from the same sign-in. There is no grace window, so refresh from one place at a time: serialise refreshes in your app rather than racing them.
  • Each rotation gives 7 more days, never past 30 days from the first token. Then the user signs in again.
  • A refresh re-reads the user's organisation and role: a member who was removed cannot refresh.

Rotate a client secret

Rotate secret issues a new secret and keeps the others working for an overlap you choose, 0 to 720 hours (24 by default; 0 ends them now). Deploy the new secret within the overlap. A single secret can also be revoked on its own. Like the first one, a new secret is shown once.

Revoking and introspecting tokens

The revocation and introspection endpoints take the app's own tokens only: an app can revoke or inspect what it was issued, never another app's.

Sign-in for terminals: the device flow

The FurniOps CLI signs people in with the OAuth device flow (RFC 8628): furniops login shows a code, opens your browser at FurniOps sign-in, and you confirm the same code there. Codes look like WDJB-MJHT and expire after 10 minutes. The device flow is for the CLI's own client; an organisation's apps use the flows above. See the CLI.

FurniOps' own apps

The dashboard, the CLI and the operations console are FurniOps' own (first-party) apps. They do not show a consent screen, and the dashboard keeps its tokens on its server, never in your browser. Everything on this page about your apps applies to anyone else's.

On this page