Awwwth Docs

Awwwth / OAuth & browser SDK

Application integration

When Awwwth uses the Keycloak backend, applications continue to use the Awwwth issuer, client credentials, signed tokens, gateway and hosted widget described here. Inline native password/MFA and Awwwth embedded passkey APIs are unavailable in this mode; use hosted sign-in.

Back to README

For automatic project setup, start with the CLI guide.

Awwwth supports confidential clients using authorization-code flow with S256 PKCE and HTTP Basic client authentication. Token exchange belongs on your application's backend because it requires the client secret. The browser SDK only obtains an authorization code.

Register an application

Use the account interface or the management API to register a platform application as an administrator, or a company application as its owner. Registration accepts:

{
  "name": "Example application",
  "redirectUri": "https://app.example.com/auth/callback",
  "logoutUri": "https://app.example.com/auth/backchannel-logout"
}

logoutUri is optional. Registration returns {id, secret}; store the secret securely on the backend. Later application listings do not return it. Each client has one exact registered callback URL. Production URLs require HTTPS, no embedded credentials, and no fragments. Development also permits HTTP on localhost and 127.0.0.1. Logout URLs cannot contain a query string.

Company clients receive a company-scoped profile and OAuth subject. Shared passwords, passkeys and MFA belong to the main Awwwth identity. Existing independent same-email company accounts require explicit proof of both accounts before linking; email matching alone never merges them.

Authorization flow

  1. Load /.well-known/openid-configuration from the issuer. Use its advertised endpoints, which may use a separate API hostname.
  2. Generate random state, nonce, and a PKCE verifier. Retain them for this login attempt. State and nonce must each be 16–512 characters; the verifier must be 43–128 characters using letters, digits, ., _, ~, or -.
  3. Redirect the browser to /oauth/authorize with the following query fields.
  4. The user signs in, verifies their email if needed, and approves access. Check callback state and issuer, and handle returned OAuth errors.
  5. Exchange the one-time code from your backend with the same callback URL and PKCE verifier.
  6. Validate the ID token before establishing your application's own session.
FieldValue
response_typecode
client_idRegistered application ID
redirect_uriExact registered callback URL
scopeSpace-separated scopes; openid required
state, nonceRandom per-attempt values
code_challengeBase64url SHA-256 digest of the verifier, without padding
code_challenge_methodS256
promptOptional login, consent, or none; none cannot be combined
max_ageOptional nonnegative integer, in seconds
screen_hintOptional signup or forgot to show a hosted account form

Supported scopes are openid, profile, email, and offline_access. Authorization requires a verified email. By default, an existing session automatically authorizes registered apps in the same account directory. prompt=none issues a code when possible and otherwise returns login_required or interaction_required; prompt=consent forces an explicit consent screen. prompt=login or max_age=0 requires authentication after the authorization request began.

The screen_hint extension opens account creation or password recovery at Awwwth instead of asking an application to implement those forms. It is intended for interactive requests and cannot be combined with prompt=none.

Token exchange and validation

Send a backend POST to the discovered token endpoint with HTTP Basic authentication using the client ID and secret, and Content-Type: application/x-www-form-urlencoded:

grant_type=authorization_code
code=<callback code>
redirect_uri=<registered callback URL>
code_verifier=<original verifier>

These are logical fields; use a form encoder such as URLSearchParams to construct the actual request body. A successful response contains access_token, token_type: "Bearer", expires_in: 300, scope, and id_token. It includes refresh_token when offline_access was requested.

Access tokens are opaque. Use /oauth/userinfo with Authorization: Bearer <access-token> for claims, or client-authenticated /oauth/introspect to check activity. Introspection only reports active access tokens belonging to the requesting client; refresh tokens are not introspected as active.

ID tokens are EdDSA-signed JWTs. Your backend must verify the signature using the discovered JWKS and check the issuer, audience, expiry, and original nonce. Relevant claims include sub, sid, auth_time, and, for company identities, tenant_id. Name and email claims depend on scopes. Merely decoding a token is not verification. Use the stable subject identifier for account linkage.

CredentialLifetime / behavior
Authorization request token10 minutes
Authorization codeOne minute; one-time exchange
Access and ID tokenFive minutes
Refresh token30 days per issued token; validity also depends on the underlying session/grant
Identity session30 days unless revoked

To refresh, send grant_type=refresh_token and refresh_token to the token endpoint with the same client authentication. Save the replacement refresh token atomically. Reuse of an already consumed refresh token revokes the grant, so serialize refresh operations per application session. Tokens remain subject to session revocation and account status.

To disconnect, send token to /oauth/revoke with client authentication. This revokes the associated grant and returns {}, including when no matching token exists.

Browser popup SDK

The ESM module at https://YOUR_ISSUER/awwwth.js exports signIn and AwwwthAuthError. Call signIn directly from a user interaction so the browser can open its popup. For example, inside your application's JavaScript module:

import { signIn, AwwwthAuthError } from "https://auth.example.com/awwwth.js";

export async function startSignIn() {
  try {
    const authorization = await signIn({
      issuer: "https://auth.example.com",
      clientId: "app_REPLACE_WITH_REGISTERED_ID",
      redirectUri: `${location.origin}/auth/callback`,
      scope: "openid profile email",
      timeoutMs: 300_000,
    });
    return authorization; // { code, codeVerifier, nonce }
  } catch (error) {
    if (error instanceof AwwwthAuthError) {
      // Let the caller render an appropriate retry or cancellation message.
      throw new Error(`Sign-in failed: ${error.code}`, { cause: error });
    }
    throw error;
  }
}

The callback must be on your application's origin and must serve a page that keeps the popup open until the SDK observes it. The SDK polls the popup URL, validates state, closes the popup, and returns the code, verifier, and nonce. It does not exchange tokens, validate ID tokens, create your app session, or supply a backend callback route. Pass the result to a backend login-completion endpoint protected and bound to the initiating browser login attempt. Keep client credentials and resulting tokens on the backend.

Options also accept an AbortSignal. Defined SDK errors are invalid_config, popup_blocked, popup_closed, request_cancelled, request_timeout, and authorization_failed; callers should also handle unexpected errors. The default timeout is five minutes. Popup isolation policies in the consuming application must preserve the opener relationship needed by this flow.

Back-channel logout

If a logoutUri is registered, grant revocation queues a server-side POST with the form-encoded field logout_token. The signed token has type logout+jwt, a two-minute expiry, and claims including iss, aud, sub, sid, iat, exp, jti, and the back-channel logout event.

Your endpoint should validate the signature, issuer, audience, expiry, event, and replay identifier, then invalidate local sessions linked to the grant's sid. Accept retries idempotently. Delivery is asynchronous and retries are bounded; applications should still enforce token expiry and handle inactive tokens. See Architecture for queue behavior.

Passkeys without app navigation

The unpublished inline package supports a visible Awwwth-origin passkey panel inside the app's page. See the module README for prepareInlinePasskey and the same-origin initiation route. The backend authenticates the registered client, keeps secrets server-side, binds a five-minute browser attempt with state/nonce/S256 PKCE and validates the normal OIDC callback. The provider permits only the exact registered callback origin to embed this panel and verifies the signed top origin. Credentials are shared through the platform identity; company profiles/permissions remain separate. Users still confirm the passkey on their device. Browser incompatibility can be recovered with the explicit hosted passkey action.