Quick Start Guide

Get your app authenticating with Keymaster in 5 minutes.

Prerequisites

Step 1: Redirect to Keymaster Login

When a user clicks "Sign In" in your app, redirect them to:

https://keymaster.cloud-monitor.com/login
  ?app_id=YOUR_APP_ID
  &redirect_uri=https://yourapp.com/auth/callback

Keymaster shows a branded login page with your app's logo, name, and enabled auth methods.

Recommended for web apps: use PKCE. Generate a random code_verifier, derive code_challenge = base64url(sha256(verifier)), stash the verifier in the user's server-side session, and add these params to the login URL:

  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

Step 2: Handle the Callback and Exchange the Code

Because your redirect_uri is a web (https) URL, Keymaster redirects back with a short-lived, single-use codenot tokens:

https://yourapp.com/auth/callback
  ?code=Xy7k...          # single-use, expires in 60 seconds

Exchange the code for tokens server-side in your callback handler:

# Server-side callback handler (Python / Flask-style)
import httpx

@app.route("/auth/callback")
def auth_callback():
    if error := request.args.get("error"):
        return f"Login failed: {error}", 400

    code = request.args["code"]

    resp = httpx.post("https://keymaster.cloud-monitor.com/token/exchange", json={
        "code": code,
        "app_id": YOUR_APP_ID,
        # Include code_verifier ONLY if you started login with PKCE:
        "code_verifier": session.pop("code_verifier", None),
    })
    resp.raise_for_status()
    tokens = resp.json()
    # {
    #   "access_token": "eyJhbGciOiJSUzI1NiIs...",
    #   "refresh_token": "a1b2c3d4e5f6...",
    #   "token_type": "Bearer",
    #   "expires_in": 900
    # }

    # Store tokens in your durable session store (see Step 4), then redirect.
    save_session(tokens)
    return redirect("/")

Do NOT read access_token from the callback URL. That was the old behavior and it no longer works for web apps — it causes an infinite login redirect loop. Web apps always exchange the ?code= at /token/exchange. (Native apps using a custom URL scheme, e.g. myapp://callback, still receive access_token and refresh_token directly on the deep link and skip this exchange.)

Step 3: Verify the Access Token

The access token is an RS256-signed JWT. Verify it using Keymaster's public keys:

# Python (using PyJWT)
import jwt
from jwt import PyJWKClient

jwks_client = PyJWKClient("https://keymaster.cloud-monitor.com/.well-known/jwks.json")
signing_key = jwks_client.get_signing_key_from_jwt(access_token)

payload = jwt.decode(
    access_token,
    signing_key.key,
    algorithms=["RS256"],
    issuer="https://keymaster.cloud-monitor.com",
    audience=YOUR_APP_ID,
)

# Reject anything that isn't an access token:
assert payload["token_type"] == "access"

# payload contains:
# {
#   "sub": "user-uuid",
#   "email": "user@example.com",
#   "name": "Jane Doe",
#   "roles": ["user", "admin"],
#   "aud": "your-app-id",
#   "iss": "https://keymaster.cloud-monitor.com",
#   "iat": 1710547200,
#   "exp": 1710548100,
#   "token_type": "access"
# }
// Node.js (using jose)
import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL('https://keymaster.cloud-monitor.com/.well-known/jwks.json')
);

const { payload } = await jwtVerify(accessToken, JWKS, {
  issuer: 'https://keymaster.cloud-monitor.com',
  audience: YOUR_APP_ID,
});

// Reject anything that isn't an access token:
if (payload.token_type !== 'access') throw new Error('not an access token');

Step 4: Store Tokens and Refresh

Store both tokens in a durable session store (database, not memory). The access token expires in 15 minutes. Before it expires, refresh it:

import httpx

resp = httpx.post("https://keymaster.cloud-monitor.com/token/refresh", json={
    "refresh_token": stored_refresh_token,
    "app_id": YOUR_APP_ID,
})
data = resp.json()
# {
#   "access_token": "new-jwt...",
#   "refresh_token": "new-refresh-token...",
#   "expires_in": 900
# }

# IMPORTANT: Update BOTH tokens in your session store.
# The old refresh token is now revoked (rotation).

The refresh token is valid for 30 days and rotates on every use. As long as the user is active within the 30-day window, their session never expires.

Step 5: Logout

When the user logs out:

# 1. Revoke the refresh token (best-effort)
httpx.post("https://keymaster.cloud-monitor.com/token/revoke", json={
    "refresh_token": stored_refresh_token,
})

# 2. Destroy local session
session.delete()

# 3. Redirect to Keymaster's branded logout page
redirect("https://keymaster.cloud-monitor.com/sso/logout"
         "?app_id=YOUR_APP_ID"
         "&post_logout_redirect_uri=https://yourapp.com")

What's Next?