Developer Documentation

INTEGRATION GUIDELINES

Integration protocols and specifications for Savelo Identity Provider (IdP) authentication.

Overview & Architecture

Savelo IdP eliminates passwords entirely using a cryptographic dual-channel mechanism:

Zero-Password Authentication

Users login via 6-digit CSPRNG numeric OTP (salted with HMAC-SHA256) or Magic Links.

Cross-Device SSE Sync

Confirming a login on a secondary mobile device triggers instant push to the initiating desktop via Redis SSE.

OIDC & PKCE S256

Strict RFC 7636 PKCE code challenges mitigate authorization code interception attacks.

Client Registration

Before your service can authenticate users, provision your application in the Developer Console:

  1. Open the Developer Console and click "Register Application".
  2. Enter your service name (e.g. Saveroute API) and whitelisted callback URLs.
  3. Copy your Client ID and Client Secret. The secret is hashed with SHA-256 and will never be shown again.
  4. Store credentials in your service environment variables.

PKCE Authorization Flow

When a user initiates authentication, redirect them to the authorization endpoint:

GET /oauth/authorize
curl -X GET "http://localhost:3000/oauth/authorize?\
client_id=savelo-tools&\
redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fapi%2Fauth%2Fcallback&\
response_type=code&\
state=xyz123&\
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&\
code_challenge_method=S256"

Token Exchange & Rotation

Upon completing verification, Savelo IdP redirects back to your callback URL with an authorization_code. Exchange this code for tokens at POST /api/oauth/token:

// Next.js App Router: app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
  const code = req.nextUrl.searchParams.get("code");
  const state = req.nextUrl.searchParams.get("state");
  const verifier = req.cookies.get("pkce_verifier")?.value;

  if (!code || !verifier) {
    return NextResponse.json({ error: "Missing authorization code or verifier" }, { status: 400 });
  }

  const tokenRes = await fetch("http://localhost:3000/api/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      client_id: process.env.SAVELO_CLIENT_ID!,
      client_secret: process.env.SAVELO_CLIENT_SECRET!,
      code,
      redirect_uri: process.env.SAVELO_REDIRECT_URI!,
      code_verifier: verifier,
    }),
  });

  const tokens = await tokenRes.json();
  // tokens: { access_token, id_token, refresh_token, token_type: "Bearer", expires_in: 900 }

  return NextResponse.redirect(new URL("/dashboard", req.url));
}

Standard Endpoints

GET/api/.well-known/openid-configuration
Discovery document for OIDC clients
GET/api/.well-known/jwks.json
Public RS256 RSA keys for JWT verification
POST/api/oauth/token
Authorization code & refresh token exchange
GET/api/oauth/userinfo
User profile claims (requires Bearer token)
POST/api/oauth/revoke
Revoke active sessions and token families