OpsAPI signs people in once and hands each of your apps a token it can check with a single call. Your apps never store passwords, never talk to Google and never build their own reset, lockout or two-factor screens. This note walks through the flow the two live demo apps use, step by step.

The flow

Sign-in sequence between the browser, your app, OpsAPI and GoogleBrowserthe userYour apphello-java / hello-phpOpsAPIidentity serviceGoogleoptional1Click “Log in”save a nonce in session2Redirect to OpsAPI/auth/signin?frontend_url=app&from=nonce3Hosted sign-in page: email + password (+ emailed code) or Google4With Google: consent5Back with ?code/auth/google/callback6Swap code, read verified emailserver to server7Find or create user · join workspace · sign JWT8Redirect back to the app/auth/callback?token=JWT&redirect=noncenonce must match session9GET /auth/meAuthorization: Bearer JWT10User + workspace + role11App session cookie, signed in
Solid arrows are requests, dashed ones are redirects or responses. With email and password, steps 4–6 don't happen: OpsAPI checks the password, and the emailed code when the workspace requires one, on its own page.
  1. Log in. The app makes a random nonce, keeps it in the user's session and sends the browser to OpsAPI.
  2. Hosted sign-in. GET /auth/signin?frontend_url=<app origin>&from=<nonce> shows OpsAPI's own page. If the browser already has an OpsAPI session it can go straight back with a fresh token.
  3. Google, if chosen. OpsAPI redirects to Google, swaps the returned code server to server and reads the email. Unverified Google emails are refused.
  4. User and workspace. A first-time user is created and added to the sign-up workspace; then OpsAPI signs a JWT.
  5. Hand-back. The browser goes to <app>/auth/callback?token=<JWT>&redirect=<nonce>, but only if the app's origin is on OpsAPI's trust list.
  6. Check. The app compares the nonce with its session, then calls GET /auth/me with the token as a Bearer header and starts its own session from the answer.

Never verify the JWT inside your app. It's signed with a key that never leaves OpsAPI. Asking /auth/me is the check, and it also tells you the user's workspace and role.

What each side does

ConcernOpsAPIYour app
Passwords, Google, AppleHandles all of itNever sees them
Two-factor codeEmails and checks it (always for platform admins)Nothing
Lockout and rate limitsPer account and per IPNothing
Who may receive a tokenTrusted origins onlyHas its origin allowed
Forged callback links—Nonce check, refuses with 400
SessionIts own sign-in sessionIts own cookie after /auth/me
RolesPer workspaceReads them from /auth/me

The app side in four routes

The demo apps add /login, /auth/callback and /logout. This is the Spring Boot callback from OpsApiAuthController.java, trimmed:

@GetMapping("/auth/callback")
public String callback(@RequestParam String token,
                       @RequestParam(name = "redirect", required = false) String nonce,
                       HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    Object expected = session == null ? null : session.getAttribute(NONCE);
    if (expected == null || nonce == null || !MessageDigest.isEqual(
            expected.toString().getBytes(UTF_8), nonce.getBytes(UTF_8))) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Sign-in was not started here. Go to /login.");
    }

    Map<String, Object> me = opsapi.me(token);   // GET /auth/me with Authorization: Bearer <token>

    request.changeSessionId();   // a session id seen before login must not survive it
    session.removeAttribute(NONCE);
    session.setAttribute(USER, me);
    session.setAttribute(TOKEN, token);
    return "redirect:" + appUrl + session.getAttribute(NEXT);
}

/logout?everywhere=true also sends the browser to /auth/signout, so the next sign-in in any app prompts again.

The PHP app does the same in OpsApiAuth.php. Both live in bwalia/workflow-examples.

Settings that matter on the OpsAPI side

SettingWhat it does
CORS_ALLOWED_DOMAINSA domain and all its subdomains that may call the API and receive tokens
PASSWORD_RESET_ALLOWED_ORIGINSExtra exact origins allowed to receive tokens and reset links
SIGNUP_NAMESPACE_SLUGThe workspace a first-time sign-in joins
GOOGLE_CLIENT_ID / _SECRET / _REDIRECT_URIThe Google OAuth client; the redirect URI must be registered on it

What it isn't

OpsAPI isn't an OpenID Connect or SAML provider: there are no client registrations and no /authorize or /token endpoints, so a stock OIDC client such as Spring Security's oauth2Login() can't use it directly. Apps integrate with the redirect, token and /auth/me contract above. Mobile apps post a Google or Apple ID token to POST /auth/oauth/validate instead.

Try it

The live demo runs exactly this flow with a Java app and a PHP app. Sign in to one, then open the other.