Skip to content

Social Authentication ​

Qelos supports OAuth-based social login through four providers:

ts
type SocialProvider = 'linkedin' | 'facebook' | 'google' | 'github';

Each provider must be enabled and configured (client id, client secret, allowed callback origins) by an admin in the Auth service config. Once enabled, the SDK exposes a uniform flow regardless of provider.

End-to-end flow ​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Client β”‚                β”‚  Qelos   β”‚                β”‚ Provider β”‚
β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
    β”‚                          β”‚                           β”‚
    β”‚ 1. getSocialLoginUrl(p)  β”‚                           β”‚
    β”‚ ───────────────────────► β”‚                           β”‚
    β”‚ β”‚ /api/auth/<provider>   β”‚                           β”‚
    β”‚ β”‚ ?state&returnUrl       β”‚                           β”‚
    β”‚ ◄─────────────────────── β”‚                           β”‚
    β”‚                          β”‚                           β”‚
    β”‚ 2. browser navigates to /api/auth/<provider>         β”‚
    β”‚ ────────────────────────►│                           β”‚
    β”‚                          β”‚ 3. 302 β†’ provider auth    β”‚
    β”‚                          β”‚ ────────────────────────► β”‚
    β”‚                          β”‚                           β”‚
    β”‚ 4. user authorizes on provider                       β”‚
    β”‚                          β”‚                           β”‚
    β”‚                          β”‚ 5. 302 β†’ /api/auth/<p>/callback?code=…
    β”‚                          β”‚ ◄──────────────────────── β”‚
    β”‚                          β”‚                           β”‚
    β”‚                          β”‚ 6. exchange code, mint    β”‚
    β”‚                          β”‚    refresh token (rt)     β”‚
    β”‚ 7. 302 β†’ returnUrl?rt=…  β”‚                           β”‚
    β”‚ ◄────────────────────────│                           β”‚
    β”‚                          β”‚                           β”‚
    β”‚ 8. exchangeAuthCallback(rt)                          β”‚
    β”‚ ───────────────────────► β”‚                           β”‚
    β”‚                          β”‚ 9. verify rt, set cookie  β”‚
    β”‚                          β”‚    session, return user   β”‚
    β”‚ ◄─────────────────────── β”‚                           β”‚
    β”‚                          β”‚                           β”‚

The browser is never given an access token directly. Step 7 hands it a single-use refresh token in the URL; step 8 immediately swaps that for a proper cookie session and the URL parameter is discarded.

Step 1 β€” Build the login URL ​

ts
const url = sdk.authentication.getSocialLoginUrl('google', {
  state: crypto.randomUUID(),       // CSRF token (recommended)
  returnUrl: 'https://app.example.com/auth/finish',
});
OptionPurpose
stateOpaque CSRF value. Persist it client-side (e.g. sessionStorage) and verify on return.
returnUrlWhere Qelos should redirect after step 7. Defaults to the configured app origin. The host must be allow-listed in the auth service.

getSocialLoginUrl() returns an absolute URL β€” it does not redirect. To trigger the navigation, either set location.href yourself or use the shorthand:

ts
sdk.authentication.startSocialLogin('google', {
  state,
  returnUrl: 'https://app.example.com/auth/finish',
});

startSocialLogin() throws if called outside a browser environment.

Step 7/8 β€” Handle the callback ​

After the user authorizes the app, Qelos redirects the browser to your returnUrl with two query parameters:

  • rt β€” the single-use refresh token to exchange.
  • state β€” echoed back from step 1, if you supplied it.

On the page mounted at returnUrl:

ts
const params = new URLSearchParams(location.search);
const rt = params.get('rt');
const returnedState = params.get('state');

if (returnedState !== sessionStorage.getItem('oauth_state')) {
  throw new Error('state mismatch β€” possible CSRF');
}
sessionStorage.removeItem('oauth_state');

if (!rt) throw new Error('missing refresh token');

const { payload } = await sdk.authentication.exchangeAuthCallback(rt);
// payload.user, payload.workspace
// session cookie is now set on the response

exchangeAuthCallback() posts the refresh token to POST /api/auth/callback?rt=…, the server verifies it, mints a cookie session, and returns the user payload. Strip rt from the URL afterward so it does not leak into history, analytics, or the Referer header:

ts
history.replaceState({}, '', location.pathname);

Errors ​

SymptomCause
Redirect loop ending at /api/auth/<p>?error=disabledProvider not enabled in the auth config
400 from exchangeAuthCallbackMissing rt parameter
401 from exchangeAuthCallbackRefresh token invalid, expired, or already exchanged
Stuck on provider pageCallback URL not whitelisted with the provider

What the user gets ​

A successful exchange leaves the SDK in the same state as a successful email/password signin β€” a cookie session is active, and the user can be re-fetched via sdk.authentication.getLoggedInUser().

To use OAuth tokens (bearer pair) instead of a cookie session for social logins, use the OAuth signin path on your own backend after exchanging the refresh token via sdk.authentication.refreshToken(rt). Most apps should stick with the cookie flow.

Build SaaS Products Without Limits.