Skip to main content

Authentication

ApexOS has a built-in user system. Browsers, the ApexConnect apps, and third-party clients all obtain tokens through the same OAuth2-style flow, then call the REST and WebSocket APIs with a bearer access token.

Auth providers

Login credentials are checked by pluggable auth providers. The providers shipped in core:

TypeTitlePurpose
apexosApexOS LocalDefault username/password store, managed on-device
trusted_networksTrusted NetworksGrants access based on the client's source network
command_lineCommand Line AuthenticationDelegates credential checks to an external command
insecure_exampleExample provider for development only

Multi-factor modules (e.g. TOTP) can be layered on top of a provider; when enabled they add extra steps to the login flow.

GET /auth/providers returns the providers available for login (no auth required):

[{"name": "ApexOS Local", "id": null, "type": "apexos"}]

Token model

  • Access tokens are short-lived JWTs (30-minute expiration, reported as expires_in: 1800), sent as Authorization: Bearer <token>.
  • Refresh tokens are long strings bound to a user and a client_id; they are used to mint new access tokens and can be revoked individually. A 90-day refresh-token expiration constant is defined in the auth module, and clients can manage expiry via the auth/refresh_token_set_expiry WebSocket command.
  • Long-lived access tokens are a special refresh-token type (long_lived_access_token) whose access token lives for a caller-chosen number of days. They are intended for scripts and integrations.

Authorization flow

The flow is OAuth2 authorization-code shaped, with IndieAuth-style client identification (no client registration step — the client ID is a URL).

1. Authorize page

Direct the user to the authorize page served by the frontend:

http://apexos.local:1702/auth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI

The page runs the login flow described below and, on success, redirects back to redirect_uri with an authorization code.

Server metadata is discoverable per RFC 8414 at /.well-known/oauth-authorization-server (authorization_endpoint, token_endpoint, and issuer when a base URL is configured); a companion document is served at /.well-known/oauth-protected-resource.

2. Login flow (used by the authorize page)

POST /auth/login_flow creates a flow. Parameters: client_id and redirect_url (validated against the client-ID rules below), handler ([provider_type, provider_id]), and optional type ("authorize" by default, or "link_user" to attach the credential to an existing user):

{
"client_id": "https://apexos.local:1702/",
"handler": ["apexos", null],
"redirect_url": "https://apexos.local:1702/",
"type": "authorize"
}

The response is a data-entry-flow step (a form schema, e.g. username/password). POST /auth/login_flow/{flow_id} progresses the flow — most providers finish in one step, but MFA can add more. The final step has type create_entry and its result is the authorization code.

3. Token exchange

POST /auth/token is the OAuth2 token endpoint. Data must be sent x-www-form-urlencoded.

Grant type authorization_code:

curl -X POST http://apexos.local:1702/auth/token \
-d "grant_type=authorization_code" \
-d "code=411ee2f916e648d691e937ae9344681e" \
-d "client_id=https://apexos.local:1702/"
{
"access_token": "ABCDEFGH",
"expires_in": 1800,
"refresh_token": "IJKLMNOPQRST",
"token_type": "Bearer",
"apexos_auth_provider": "apexos"
}

(apexos_auth_provider is the literal response field name; it carries the auth provider type that authorized the refresh token.) Responses are sent with Cache-Control: no-store.

Grant type refresh_token:

curl -X POST http://apexos.local:1702/auth/token \
-d "grant_type=refresh_token" \
-d "refresh_token=IJKLMNOPQRST" \
-d "client_id=https://apexos.local:1702/"
{"access_token": "ABCDEFGH", "expires_in": 1800, "token_type": "Bearer"}

The client_id must match the one the refresh token was issued to. Errors follow OAuth2 conventions: invalid_request, invalid_grant, unsupported_grant_type (HTTP 400) and access_denied (HTTP 403, e.g. for deactivated users).

4. Revocation

POST /auth/revoke with token=<refresh_token> revokes the refresh token and every access token it ever granted. The response is always 200. (Sending action=revoke to /auth/token still works for backwards compatibility but is deprecated.)

Linking additional credentials

POST /auth/link_user (authenticated) consumes an authorization code produced by a link_user-type login flow and attaches the new credential to the current user.

Client IDs and redirect URIs (IndieAuth-style)

There is no client registry: the client_id is a URL, validated per IndieAuth client-identifier rules:

  • Scheme must be http or https; hostnames are lowercased and an empty path is treated as /.
  • Must not contain . or .. path segments, a fragment, or a username/password component.
  • May contain a port.
  • The host must be a domain name or a loopback address (127.0.0.1 / [::1]); other literal IPs are rejected.

The redirect_uri is accepted when it has the same scheme and authority as the client_id. Otherwise, the server fetches the client_id URL (first 10 kB) and looks for published <link rel="redirect_uri"> tags; after resolving relative URLs, the redirect URI must match one of them exactly.

Fixed allowances exist so the official ApexConnect apps can link without internet access:

  • client https://secure.auth.apexinfosys.in/iOSapexconnect://auth-callback
  • client https://secure.auth.apexinfosys.in/dev-authapexconnect-dev://auth-callback
  • client https://secure.auth.apexinfosys.in/androidapexos://auth-callback plus the wearable-platform 3p-auth callback URLs for the companion Android package

Auth over WebSocket

After a WebSocket connection authenticates, these commands are available:

  • auth/current_user — returns the connection's user (id, name, owner flag, credentials, MFA modules).

  • auth/long_lived_access_token — creates a long-lived access token for the current user; parameters client_name (string) and lifespan (integer days). The token is returned once and never stored in retrievable form — record it securely:

    {"id": 11, "type": "auth/long_lived_access_token", "client_name": "GPS Logger", "lifespan": 365}
  • auth/refresh_tokens — lists the user's refresh tokens (client names, last use, etc.).

  • auth/delete_refresh_token / auth/delete_all_refresh_tokens — revoke tokens.

  • auth/sign_path — returns a temporarily signed URL path (the signature travels in the authSig query parameter) for endpoints that cannot send headers, such as media URLs.

  • auth/refresh_token_set_expiry — enables/disables expiry for a refresh token.