Skip to Content
API ReferenceAuth Apps

Auth Apps

Manage authentication services for your applications (Firebase Auth alternative). All management endpoints require Authorization: Bearer TOKEN.

These endpoints manage the end-users of apps you’ve built on top of the platform. To manage your own Ghayma account (profile, password, email), see Account. For the public sign-in endpoints your app calls (register, login, refresh, verify JWT), see the Auth Integration Guide.

Create Auth App

POST /api/v1/auth-apps

Body:

{ "name": "My App Auth", "app_id": "my-app", "project_id": "prj_abc123" }
FieldTypeDescription
namestringRequired. Display name, 2–50 chars
app_idstringRequired. URL slug, 3–30 lowercase alphanumeric characters or hyphens. Globally unique and immutable
project_idstringRequired. Project id or slug
auth_tier_slugstring?Capacity bracket; defaults to the smallest
team_idstring?Owning team
connect_site_idsstring[]?Site uuids of the project to connect the new app to, at level client. Omitted or empty → connected to no site
allowed_originsstring[]?Origins outside the project to accept — see Allowed origins. Omitted → none

app_id is the app’s public identifier: it is the slug in every auth URL (https://auth.ghayma.tech/v1/my-app/login) and the aud claim of every JWT the app issues.

Response: 201

{ "auth_app": { "id": "aap_abc123", "app_id": "my-app", "name": "My App Auth" }, "endpoint": "https://auth.ghayma.tech/v1/my-app", "connections": { "connected": ["<site-uuid>"], "pending": [], "failed": [] } }

Only the sites in connect_site_ids receive the app’s variables. They are checked before anything is created, and connections reads as it does for a database.

OAuth providers are not configured at creation. Enable them afterwards with Update Auth App, in the Dashboard, or via ghayma auth config.

List Auth Apps

GET /api/v1/auth-apps

Get Auth App

GET /api/v1/auth-apps/:id

Update Auth App

PUT /api/v1/auth-apps/:id

Body: any subset of the writable fields below. Unknown keys are ignored, and app_id / project_id cannot be changed.

FieldTypeDescription
namestringDisplay name
allowed_originsstring[]Origins outside the project to accept; replaces the list — see Allowed origins
email_verification_requiredbooleanBlock login until the email is verified
jwt_expiry_secondsnumberAccess-token lifetime (default 900)
refresh_expiry_secondsnumberRefresh-token lifetime (default 604800)
google_oauth_enabledbooleanEnable Google sign-in
google_client_id / google_client_secretstringGoogle OAuth credentials
github_oauth_enabledbooleanEnable GitHub sign-in
github_client_id / github_client_secretstringGitHub OAuth credentials
email_localestringEnd-user email language: en, fr, ar
reset_urlstringYour own password-reset page, https on an allowed origin (http only on localhost)
auth_tier_slugstringCapacity bracket (re-prices the app)
two_fa_enabledbooleanOffer TOTP two-factor auth (the authenticator-app method, a priced add-on). A JSON boolean only
two_fa_policystringdisabled, optional or enforced — see Two-factor policy

See the OAuth Providers guide for the provider setup flow.

Allowed origins

One set of origins decides CORS on the auth endpoints, which OAuth redirect_uri is accepted, and where reset_url may point. It is:

  • Every domain of every site in the app’s project, as https://<hostname>: the .ghayma.app URLs and your custom domains. A domain added later counts within a minute, with no change to the app. A custom domain still waiting for DNS counts once it is live.
  • Local development: http://localhost and http://127.0.0.1, any port.
  • The entries in allowed_origins.

So allowed_origins is only for origins outside the project: a frontend hosted elsewhere, a preview URL, or a mobile app’s deep link such as com.example.app://callback. A custom scheme is accepted only from an exact entry.

Each entry is scheme://host[:port], with no path and no trailing slash, and is stored lowercase. "*" is no longer accepted. A refused list answers 400:

errorWhen
"*" is no longer allowed: your project's sites and localhost are always allowed; list any other origin explicitlyThe list contains "*"
allowed_origins: "https://app.example.com/" is not an origin; use scheme://host[:port] with no path, like https://app.example.comAn entry with a path, a query or a trailing slash, or that is not an origin (the entry is quoted)

Every response that returns the app carries a read-only site_origins field: the project’s domains that are always allowed. The console shows them under Allowed Origins → Always allowed.

"*" no longer allows every origin: it matches nothing, and responses leave it out. An app that relied on it keeps working from its project’s domains and localhost; list any other origin that calls it.

Two-factor policy

two_fa_policy decides who is asked for a second factor, at the password login and at every Google or GitHub sign-in:

  • disabled (the default) — nobody.
  • optional — users who enrolled.
  • enforced — everyone: a user without a second factor enrols at their next sign-in.

optional and enforced need the method on (two_fa_enabled: true), and the method cannot be turned off while either is set: set the policy to disabled first, or in the same request. A refused update answers 400 with one of:

errorWhen
two_fa_enabled must be a booleantwo_fa_enabled is anything but true / false — a string "true" included
two_fa_policy must be one of: disabled, optional, enforcedAn unknown policy
two_fa_policy "enforced" requires the authenticator-app 2FA method to be enabled (two_fa_enabled)Setting optional or enforced while the method is off
the authenticator-app 2FA method cannot be turned off while two_fa_policy is "enforced"; set two_fa_policy to disabled firsttwo_fa_enabled: false while the policy is optional or enforced

The quoted policy in the last two messages is the one in play ("optional" or "enforced").

Update the SDK of every app that signs users in with Google or GitHub before you set optional or enforced: older versions break those sign-ins for the users the policy applies to. See Minimum versions for 2FA.

Delete Auth App

DELETE /api/v1/auth-apps/:id

Get Statistics

GET /api/v1/auth-apps/:id/stats

Response: 200

{ "total_users": 1542, "active_users": 890, "logins_today": 87, "signups_today": 12 }

Rotate Signing Keys

Invalidates all existing user tokens.

POST /api/v1/auth-apps/:id/rotate-keys

List Users

GET /api/v1/auth-apps/:id/users?page=1&limit=20&search=john
ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber20Results per page
searchstring—Search by name or email

Get User

GET /api/v1/auth-apps/:id/users/:userId

Disable / Enable User

Separate routes, no request body.

POST /api/v1/auth-apps/:id/users/:userId/disable POST /api/v1/auth-apps/:id/users/:userId/enable

Disabling ends every session of the user at once, and a disabled user cannot sign in. Re-enabling lets them sign in again; nothing from before the disable comes back.

Set User App Metadata

Developer-owned data (roles, plan, tenant id) that is embedded in the user’s JWT as the app_metadata claim. Replace semantics — send the full object; {} clears it. Max 8 KB.

PATCH /api/v1/auth-apps/:id/users/:userId/app-metadata

Body:

{ "app_metadata": { "roles": ["admin"], "plan": "pro" } }

Requires the admin or owner role on the project. Changes reach the user’s tokens at their next refresh.

Reset a User’s 2FA

Clears the user’s two-factor enrolment so a locked-out user can sign in again — with a password, Google or GitHub — and re-enrol. It is also the way out for a user asked for a code they never set up after a Google or GitHub sign-in took over their account. Requires the admin or owner role; audited.

POST /api/v1/auth-apps/:id/users/:userId/reset-2fa

Delete User

DELETE /api/v1/auth-apps/:id/users/:userId