Skip to content

Use Udibo's identity service

Udibo handles sign-in and issues tokens for your application. Your backend completes the OAuth2 callback, maintains an application session, and protects application data.

Udibo is in private beta. Join the waitlist. The configuration below is for developers who already have beta access. Public registration and its setup instructions will be documented when access opens. To develop without an account, use the local identity provider or the external-auth example.

What you need

Obtain these values for your application through your beta setup:

ValueUse
Issuer URLThe identity service's issuer, copied exactly; it may differ from your app's origin
Client ID and secretA confidential client registration for your backend
Callback URLAn exact registered URL, such as https://app.example.com/auth/callback
Allowed scopesIdentity claims and API permissions your application may request
Introspection or JWKS configurationToken validation for your API; use the mode configured for the application

Use separate registrations and secrets for development and production. Keep client credentials on the backend. The issuer and client ID are identifiers; a client secret is a credential.

Put the BFF on your app's origin

Mount the BFF at /auth. The browser talks to your own backend using its session cookie. It never needs your client secret or refresh token.

The following is integration wiring. sessions is a persistent SessionStore you implement for your app; resourceServer validates access tokens using the API protection guide. Both are declared here so the example makes its application-owned dependencies explicit.

TypeScript
import { Hono } from "hono";
import { DirectClient } from "@udibo/oauth2/client";
import {
  EncryptedCookieAuthRequestStorage,
  HonoBff,
  type SessionStore,
} from "@udibo/oauth2/hono/bff";
import type { HonoResourceServer } from "@udibo/oauth2/hono/resource-server";
import { requestLogger } from "@udibo/oauth2/hono/log";
import type { ClientInterface } from "@udibo/oauth2/server";

declare const sessions: SessionStore;
declare const resourceServer: HonoResourceServer<ClientInterface, unknown>;

function required(name: string): string {
  const value = Deno.env.get(name);
  if (!value) throw new Error(`Missing ${name}`);
  return value;
}

const client = new DirectClient({
  issuer: required("AUTH_ISSUER"),
  clientId: required("AUTH_CLIENT_ID"),
  clientSecret: required("AUTH_CLIENT_SECRET"),
  redirectUri: required("AUTH_CALLBACK_URL"),
});

const bff = new HonoBff({
  client,
  sessionStore: sessions,
  authRequestStorage: new EncryptedCookieAuthRequestStorage({
    secret: required("AUTH_REQUEST_SECRET"),
  }),
  resourceServer,
  scope: required("AUTH_SCOPES"),
  defaultReturnTo: "/",
});

const app = new Hono();
app.use(requestLogger());
app.route("/auth", bff.routes());
app.use("/api/*", bff.protect());
app.get("/api/message", (c) => c.json({ message: "Authenticated" }));

export default app;

AUTH_REQUEST_SECRET is an app-generated high-entropy secret shared by every instance of this application. It protects the pending state and PKCE verifier across the redirect. Store it in your deployment's secret manager. The example keeps the BFF's HTTPS cookie and CSRF defaults enabled.

requestLogger() redacts every query value on both log lines, including callback code and state. Configure reverse proxies and tracing separately; this middleware cannot redact logs emitted by other systems.

Use bff.protect("read") for a scope required throughout an API mount, or layer resourceServer.requireScope(...) on individual routes after authentication. Authentication alone does not check whether a user owns a specific record; that authorization check belongs in your application.

For a separate API process, use bff.proxy(fixedApiUrl, options) and validate tokens in that API. See the proxy example.

Connect the browser

TypeScript
import { BffClient } from "@udibo/oauth2/client";

const auth = new BffClient();
const session = await auth.getSession();
if (!session.isAuthenticated) {
  const { url } = await auth.login({ returnTo: "/" });
  location.assign(url);
}

const response = await auth.fetch("/api/message");

BffClient sends the BFF's CSRF header. A custom fetch client must send the same header on credentialed requests. Do not disable CSRF to make a browser request work.

React apps can use OAuth2Provider, useOAuth2, and RequireAuth from @udibo/oauth2/react; the external-auth Juniper example shows that wiring. A client-side guard controls rendering; your API still requires server-side authorization.

The BFF's local logout ends the application session and attempts upstream token revocation. Ending the identity provider's SSO session is a separate operation; configure and verify that behavior for your application if you need it.

Test the integration

Before pointing it at production:

  • Complete sign-in and sign-out in a real browser with the registered callback.

  • Verify unauthenticated requests receive 401 and insufficient scopes receive 403.

  • Expire an access token and check that a refresh preserves the session.

  • Revoke the session and check that an in-flight refresh cannot restore it.

  • Run runSessionStoreContractTests from @udibo/oauth2/hono/bff/testing against your store. Stateful updates must reject expired, missing, or revoked sessions.

  • Confirm callbacks work when login and callback requests reach different application instances.

Troubleshooting

SymptomCheck
Callback rejected before sign-inExact callback registration: scheme, host, port, path, and query
invalid_clientBackend client ID/secret and whether this is the correct registration
Unknown state or missing login cookieShared pending-login storage, cookie attributes, and whether callback uses the same browser
Credentialed BFF request gets 403CSRF header and same-origin request configuration
Refresh gives invalid_grantExpiry or revocation; start a new login instead of retrying the credential indefinitely
API rejects a tokenIssuer, intended audience, token format, and required scopes

See environment configuration and application deployment for the remaining app-side setup. Hosted-service administration and account provisioning are documented separately from this package.