Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.skyvexsoftware.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Stratos supports two authentication methods for pilots: Credentials (username and password sent through the Stratos VA API) and OAuth 2.0 with PKCE (the pilot is redirected to your crew system, signs in there, and Stratos receives an access token in return). OAuth is strongly recommended. Pilots never enter their crew system password into Stratos, you keep full control over the login UI, and you can revoke a pilot’s Stratos access from your own admin tools without touching Skyvex. This guide covers the full lifecycle: the initial sign-in, what each endpoint must return, how access tokens are refreshed silently in the background, and the security controls your crew system must enforce.
Stratos is a public client — it runs on each pilot’s desktop and cannot keep a client secret. You must register Stratos as a public OAuth client on your crew system and require PKCE on the authorisation code exchange. Do not issue a client secret.

How OAuth in Stratos Works

There are three distinct moments where Stratos talks to your OAuth endpoints. Most VA admins only think about the first one — but the second and third are where the user experience lives or dies.
1

Initial sign-in (once per pilot per device)

The pilot picks your airline in Stratos and clicks Sign In. Stratos opens their default browser to your Authorise URL with a PKCE code challenge. The pilot signs in to your crew system and approves access. Your crew system redirects the browser back to a stratos:// URL with an authorisation code. Stratos exchanges that code (plus the PKCE verifier) at your Token URL and receives an access token and a refresh token.
2

Every API request (constant)

Stratos sends the access token as a Bearer token on every call to your VA API. Your API validates the token and returns the resource. The pilot sees nothing — this just happens.
3

When the access token expires (every 30–60 minutes)

Your access token expires. The next API call returns 401. Stratos POSTs the refresh token to your Refresh Token URL, gets a fresh access token, retries the failed call, and the pilot never knows anything happened. If you do not provide a Refresh Token URL, Stratos has no way to silently recover and will pop the sign-in browser back open — which is a poor experience mid-flight.

Prerequisites

Before you start, your crew system needs to support:
  • OAuth 2.0 Authorisation Code grant
  • PKCE (RFC 7636), with S256 code challenge method as the minimum
  • Public clients (no client secret)
  • A redirect URI scheme that allows the custom stratos:// URI and an https:// URI
  • Refresh tokens with the refresh_token grant type (strongly recommended)
If you run Laravel Passport, all of the above is supported out of the box. Most modern OAuth servers (Authentik, Keycloak, Auth0, Okta, Hydra, your own Doorkeeper/django-oauth-toolkit setup) support PKCE public clients too — but check before assuming.

Step 1 — Register Stratos as a Public OAuth Client

Inside your crew system’s OAuth admin, create a new client with these settings:
FieldValue
Client nameStratos (or whatever you’d like pilots to see on the consent screen)
Client typePublic (no secret)
Grants enabledauthorization_code, refresh_token
PKCE requiredYes
Redirect URIsThe two URIs shown in your Skyvex airline settings (see below)

Redirect URIs

When you open your airline’s settings on Skyvex and select OAuth 2.0 (PKCE), Skyvex shows you two redirect URIs that you must register on your OAuth client:
stratos://auth/airline/{your-airline-id}/callback
https://skyvexsoftware.com/auth/airline/{your-airline-id}/callback
Both must be allowed. The stratos:// URI is used by the desktop app on Windows, macOS, and Linux. The https:// URI is used as a fallback for the web-based test flow on the Skyvex platform.
Register the URIs exactly as Skyvex shows them, including the airline ID segment. OAuth servers do exact-match comparison and will refuse to redirect on a mismatch.

Laravel Passport Example

If you’re using Passport on the crew system side, the equivalent Artisan command is:
php artisan passport:client \
  --public \
  --name="Stratos" \
  --redirect_uri="stratos://auth/airline/YOUR-AIRLINE-ID/callback,https://skyvexsoftware.com/auth/airline/YOUR-AIRLINE-ID/callback"
Passport requires PKCE on public clients automatically — you do not need additional configuration.

Step 2 — Configure Skyvex

Open your airline in the Skyvex platform, go to Crew System Integration, and select OAuth 2.0 (PKCE) as the authentication method. Fill in these fields:
FieldWhat to enter
Client IDThe public client ID issued by your OAuth server in Step 1.
Scopes (optional)Comma-separated list, e.g. name, email. Leave blank if your crew system does not use scopes.
Authorise URLThe URL Stratos opens in the pilot’s browser to start sign-in. e.g. https://crew.example.com/oauth/authorize
Token URLThe URL Stratos calls to exchange an authorisation code for an access token. e.g. https://crew.example.com/oauth/token
Refresh Token URL (optional but strongly recommended)The URL Stratos calls to silently refresh an expired access token. In almost every implementation this is the same URL as the Token URL — Passport, Authentik, Keycloak, Auth0, Okta and most others all use a single /oauth/token endpoint that handles both grants.
Click Save, then click Test Connection. Skyvex will run through the full flow once and show Verified with a date if everything works. If you change any of the OAuth fields after verification, the verified status is cleared and you’ll need to re-test.
Should you fill in the Refresh Token URL? Yes, unless you have a deliberate reason not to. Without it, every pilot has to interactively re-authorise every time their access token expires — typically every 30–60 minutes. With it, the refresh happens silently in the background and the pilot only signs in once.

Endpoint Reference — What Stratos Sends

This section documents every request Stratos will make to your OAuth endpoints. If your crew system follows RFC 6749 + RFC 7636, this matches what you already implement — it’s here for debugging and for custom in-house OAuth servers.

Authorise Endpoint (Authorise URL)

Stratos opens this URL in the pilot’s default browser:
GET https://crew.example.com/oauth/authorize
  ?response_type=code
  &client_id=<your-public-client-id>
  &redirect_uri=stratos://auth/airline/<airline-id>/callback
  &scope=<comma-separated scopes if configured>
  &state=<random opaque value>
  &code_challenge=<base64url-sha256 of verifier>
  &code_challenge_method=S256
After the pilot signs in and approves, your crew system must redirect to the redirect_uri with ?code=<auth code>&state=<the same state>. If the pilot denies or an error occurs, redirect with ?error=access_denied&state=<state>.

Token Endpoint — Authorisation Code Grant (Token URL)

Immediately after Stratos receives the auth code, it exchanges it:
POST https://crew.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=<your-public-client-id>
&redirect_uri=stratos://auth/airline/<airline-id>/callback
&code=<the auth code>
&code_verifier=<the PKCE verifier>
Your crew system must verify the PKCE verifier matches the challenge from the authorise step, then respond with:
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...",
  "scope": "name email"
}
refresh_token should only be issued if you’ve configured a Refresh Token URL on Skyvex. If you don’t issue a refresh token, do not include the field.

Refresh Endpoint (Refresh Token URL)

When the access token expires, Stratos sends:
POST https://crew.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&client_id=<your-public-client-id>
&refresh_token=<the refresh token>
Note: no client secret — Stratos is a public client. The refresh token itself is the credential. Your crew system must respond with a fresh access token, and (best practice — see the security section below) a new refresh token, invalidating the one that was just used:
{
  "access_token": "...new...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...new..."
}
If the refresh token is invalid, expired, revoked, or has already been used (in a rotating-token setup), respond with HTTP 400 and:
{ "error": "invalid_grant" }
Stratos treats invalid_grant as terminal — it wipes its stored tokens and prompts the pilot to sign in again. Do not retry-loop on this error.

Security — What Your Crew System Must Enforce

The refresh token endpoint is the most sensitive piece of the OAuth flow. Stolen access tokens die in 30–60 minutes; a stolen refresh token, without proper controls, is good for as long as the token’s lifetime — possibly months. The list below is the minimum we recommend every VA implements.
Every time a refresh token is exchanged, invalidate it and issue a new one. The client (Stratos) must always be holding the latest refresh token; previous tokens must be unusable.Laravel Passport rotates refresh tokens by default. Most OAuth servers do too — but verify this is on for your stack.
If a refresh token that has already been rotated is presented again, that’s a strong signal of theft — either an attacker stole the token, or there’s a bug. Either way, the safe response is to revoke the entire token family for that pilot (the original token and every token derived from it) and force them to sign in again.This is the single highest-leverage control on the list. Passport supports this; some other servers require additional configuration.
Set a hard upper bound — 30 days is a reasonable default. Even if a refresh token is silently exfiltrated and never reused (so reuse detection doesn’t trigger), the pilot will be forced to do an interactive sign-in at most 30 days later. That bounds the damage.For Passport: Passport::refreshTokensExpireIn(now()->addDays(30));
Throttle requests by client ID, by IP, and ideally per refresh token. A single Stratos client legitimately refreshes once per access-token lifetime — at most ~50 times a day. Anything dramatically above that is suspicious.Laravel example: apply the throttle:60,1 middleware to your /oauth/token route.
When the pilot changes their password, gets suspended, leaves the airline, or has their role downgraded, revoke all of their refresh tokens. Otherwise an old refresh token will keep producing valid access tokens until natural expiry, regardless of what your admin tools say.
Only allow the exact two redirect URIs that Skyvex shows you. Wildcard or pattern-matched redirect URIs on a public client are a serious security risk — they let an attacker who tricks a pilot into clicking a crafted URL receive the pilot’s authorisation code.
At minimum: pilot ID, IP, user agent, timestamp, success/failure. This gives you a forensic trail if a pilot reports their account was misused, and lets you spot patterns (e.g. a single account being refreshed from many IPs at once).

Testing Your Setup

The Test Connection button in your Skyvex airline settings runs a full sign-in flow against your live OAuth server using your own admin browser session. It does the following, in order:
  1. Opens your Authorise URL in a popup with PKCE.
  2. Receives the auth code at the Skyvex web callback.
  3. Exchanges the code for an access token at your Token URL.
  4. (If a Refresh Token URL is set) Immediately performs one refresh against the Refresh Token URL to verify the refresh path works.
  5. Marks your OAuth config as Verified with the current date.
If any step fails, Skyvex will surface the error so you can fix it before pilots run into it. We strongly recommend running the Test Connection flow whenever you change any OAuth setting on either side — the verification is automatically cleared on save, so this also serves as a deliberate “I’ve checked this” gate.

Troubleshooting

Your OAuth client doesn’t have the exact redirect URIs registered. Copy them again from the Skyvex airline settings (use the Copy button to avoid whitespace issues) and re-register them on your OAuth client. Both the stratos:// and https://skyvexsoftware.com URIs must be allowed.
Either the Client ID is wrong, or your OAuth client is registered as confidential (requiring a secret) instead of public. PKCE public clients must not require a secret. Re-register the client as public.
Almost always one of:
  • You didn’t issue a refresh token in the original token response (check that refresh_token is enabled as a grant type for the client).
  • Your refresh token rotation is on, but you’re invalidating the new token instead of the old one (token-rotation bug).
  • The refresh token has a very short TTL and expires before Stratos uses it.
Most likely the Refresh Token URL field is empty in your Skyvex airline settings. Without it, Stratos has no way to refresh access tokens silently. Fill it in (typically the same URL as your Token URL) and run Test Connection.A second possibility: your refresh tokens have a very short lifetime (e.g. 1 hour). Pilots who close Stratos for the night will be forced to sign in again every morning. Bump the refresh token lifetime to at least a few days.
This usually means the access token came through, but a subsequent VA API call rejected it. Check that your VA API is validating tokens against the same OAuth server, and that the API base URL is correct in the Skyvex airline settings.

Summary Checklist

Before you mark your OAuth setup as production-ready, confirm:
  • OAuth client is registered as public with PKCE required
  • Both redirect URIs (stratos://... and https://skyvexsoftware.com/...) are registered exactly as shown
  • refresh_token grant is enabled and refresh tokens are issued in token responses
  • Refresh Token URL is filled in on the Skyvex airline settings page
  • Refresh token rotation is enabled, with reuse detection that revokes the entire token chain
  • Refresh token lifetime is capped (30 days recommended)
  • Token endpoint is rate-limited
  • Refresh tokens are revoked on password change, role change, and account suspension
  • Test Connection in Skyvex shows Verified with today’s date