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:
| Type | Title | Purpose |
|---|---|---|
apexos | ApexOS Local | Default username/password store, managed on-device |
trusted_networks | Trusted Networks | Grants access based on the client's source network |
command_line | Command Line Authentication | Delegates credential checks to an external command |
insecure_example | — | Example 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 asAuthorization: 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 theauth/refresh_token_set_expiryWebSocket 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
httporhttps; 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/iOS→apexconnect://auth-callback - client
https://secure.auth.apexinfosys.in/dev-auth→apexconnect-dev://auth-callback - client
https://secure.auth.apexinfosys.in/android→apexos://auth-callbackplus 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; parametersclient_name(string) andlifespan(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 theauthSigquery parameter) for endpoints that cannot send headers, such as media URLs. -
auth/refresh_token_set_expiry— enables/disables expiry for a refresh token.