# Connect your first application By the end of this guide, a user will sign in on your Udibo Identity tenant, return to your application, and call an API protected by their application session. Start with one development application and one test account. ## Before you start This guide uses a backend-for-frontend (BFF), which keeps tokens and the client secret on your backend. For a browser-only or native client that cannot keep a secret, use [a public application with PKCE](https://www.udibo.com/docs/identity/applications#choose-the-client-type). If it calls your API, use [JWT access tokens with API validation](https://www.udibo.com/docs/identity/applications#choose-how-your-api-validates-tokens): hosted introspection requires the issuing application's confidential credentials. You need a **Udibo account** and a web application with a backend. Start at [Udibo](https://www.udibo.com/) for how to get an account. Without one, the [local package quickstart](https://www.udibo.com/docs/oauth2/quickstart) runs the same flow against a local identity provider. Once you are signed in, choose **Identity** in the dashboard. It opens your tenant, or asks you to create one if you have none. A tenant you create is owned by your active organization, or by you when Personal is active; creating one for an organization takes an owner membership in it. Use a separate tenant for development if you want test users and policy kept apart from production. The tenant switcher in the sidebar lists your active organization's tenants and the ones shared with you; see [your active organization](https://www.udibo.com/docs/identity/team-access#your-active-organization). If a colleague owns the tenant, ask them to [give you dashboard access](https://www.udibo.com/docs/identity/team-access). A role in your application never grants dashboard administration. For a new project, [choose a starter](https://www.udibo.com/docs/identity/starter-projects). For an existing app, add this integration alongside your current login and switch it on for test accounts first. Follow the [migration plan](https://www.udibo.com/docs/identity/migrate) before moving users. ## 1. Register the application Open your tenant's **Applications** page and choose **Create application**. For a web app whose backend handles the callback, change these fields from their defaults: | Field | Set it to | Why | | ------------------- | -------------------------------------------- | -------------------------------------------------- | | Name | A name you recognize, such as “My app (dev)” | Distinguishes this registration from production | | Environment | Development (the default is Production) | Permits an `http://localhost` callback | | Grants | Authorization code and Refresh token | Signs people in and renews their access | | Confidential client | Checked (unchecked by default) | Your backend can protect a client secret | | Redirect URIs | Your app's complete callback URL, one a line | The service returns the browser here after sign-in | Leave **Type** as First-party, which skips the consent screen for an app you build, and the collapsed **Token settings** as they are: the **Access token format** stays Opaque, which your API validates by introspection and which you can revoke instantly. For example, an app running at `http://localhost:8000` with its backend's sign-in routes mounted at `/auth` uses `http://localhost:8000/auth/callback`. Match the actual port and path, and use the same URL in your application's configuration. Save the application. **Saving creates the client ID; it does not create a secret.** On the application's page, choose **Create new secret** and store the value in your backend's secret configuration; it is shown once. Managing secrets requires the **Reveal application secrets** permission. The [application configuration guide](https://www.udibo.com/docs/identity/applications) explains token formats, scopes, production callbacks, and public clients. ## 2. Gather the configuration Keep these values together for this environment. The application's page shows the first two with copy buttons. | Value | Where it comes from | | ------------------ | ---------------------------------------------------------------------------------------- | | Issuer | **Issuer** on the application's page: `https://{tenant-id}.udibo.com` | | Client ID | **Client ID** on the application's page | | Client secret | The value shown once by **Create new secret** | | Callback URL | The redirect URI you registered, exactly | | Scopes | `openid`, plus `profile` and `email` when you need those identity claims | | Application secret | A high-entropy value you generate to protect pending sign-in state; it is not from Udibo | The issuer is your tenant's own host, not your application's host and not the dashboard URL. **Settings** → **General** also shows it as **Issuer URL**. Its discovery document at `{issuer}/.well-known/oauth-authorization-server` lists the protocol endpoints, so your client can discover them instead of copying each one. Use environment variables or your deployment's secret manager for credentials. Do not put the client secret in browser configuration, a public repository, or an agent prompt. ## 3. Connect the backend and browser Follow [Use Udibo's identity service](https://www.udibo.com/docs/oauth2/guides/use-udibo) for the checked Hono backend and browser client code. It reads the values you gathered from these environment variables: | Variable | Value | | --------------------- | ------------------------------------------------------- | | `AUTH_ISSUER` | Issuer | | `AUTH_CLIENT_ID` | Client ID | | `AUTH_CLIENT_SECRET` | Client secret | | `AUTH_CALLBACK_URL` | Callback URL | | `AUTH_SCOPES` | Scopes, space-separated, such as `openid profile email` | | `AUTH_REQUEST_SECRET` | Application secret | Using another language or framework? Any OpenID Connect client library that supports the authorization-code flow with PKCE can connect using the same values. The integration has three pieces: 1. Your backend mounts `HonoBff` at `/auth`, exchanges the authorization code, and stores the application's session. 2. Your API validates the access token. With opaque tokens, use introspection against the issuer, as shown in [Protect an API](https://www.udibo.com/docs/oauth2/guides/protect-an-api). 3. Your browser uses `BffClient` to start login and make authenticated requests to the same application origin. React applications can use the package's provider and hooks for session presentation. The guide explicitly marks the session store and resource server as dependencies you supply. A declared variable in a snippet is not a working database adapter. Use a [runnable example](https://www.udibo.com/docs/identity/starter-projects) if you want to inspect all the pieces together before adapting an existing application. ## 4. Complete a sign-in Your dashboard account administers the tenant; creating a tenant does not also create a user inside it. Use a test user from the tenant's **Users** page for this sign-in. Unless you opened sign-up when creating the tenant, it starts closed, so either choose **Create user** under **Users**, or change **Self-serve sign-up** under **Settings**. Password, email-code, and magic-link sign-in are on by default; **Security** controls which methods are offered. A closed sign-up mode does not prevent an existing user from signing in. Open your application in a browser and start login from the application. The browser should visit the tenant's hosted sign-in page, complete authentication, and return to your registered callback. Your app then reports an authenticated session. The hosted pages set cookies on your tenant's host while a person signs in. List them in your own privacy notice; [cookies on your hosted pages](https://www.udibo.com/docs/identity/cookies) names each one. **Checkpoint:** a protected request succeeds while signed in. In a separate signed-out browser context, the same protected request is refused. Signing out ends the application's session; the tenant's SSO session is a separate session, so test the logout behavior you intend to offer. ## 5. Make the first success repeatable - Repeat the flow after reloading the page and after restarting the backend - Confirm expired or revoked sessions cannot be restored by a refresh race - Confirm the browser has no access or refresh tokens in local storage - Test one operation without its required permission; it must be refused Use [troubleshooting](https://www.udibo.com/docs/identity/troubleshooting) if a checkpoint fails. Once it passes, choose [sign-in methods](https://www.udibo.com/docs/identity/sign-in-methods), add [organizations](https://www.udibo.com/docs/identity/organizations) and [permissions](https://www.udibo.com/docs/identity/permissions) where needed, and work through the [production checklist](https://www.udibo.com/docs/identity/production). ## Optional: start at sign-up instead of sign-in A consumer product's primary call to action is "Sign up", and an authorization request can say so. Add OIDC's `prompt=create` and a signed-out person lands on your tenant's sign-up page instead of its sign-in page, carrying the same authorization request — so the account they create completes that request and returns to your callback with a credential, rather than finishing on your tenant's host with no flow in progress. ``` GET https://{your-tenant-host}/api/oauth2/authorize?…&prompt=create ``` Through `@udibo/oauth2`'s backend-for-frontend, configure `forwardedParams: ["prompt"]` on `HonoBff`, then send the person to `/auth/login?prompt=create`. Browser-supplied `scope` and `prompt` are ignored unless the application explicitly allows them. Keep `scope` server-configured; it does not need forwarding for sign-up. Allowing `prompt` also lets the browser choose its other values, so use a fixed `extraParams: { prompt: "create" }` instead when every login through that BFF should start at sign-up. Three rules to know: - **Someone already signed in is sent straight on**, as if no prompt were given. The parameter chooses where an unauthenticated request starts; it does not sign anyone out or create a second account. - **`prompt=create` with `prompt=none` is refused** with `invalid_request` on the standard error redirect: the two ask for opposite things. - **It is not a sign-up-mode check.** A tenant whose sign-up mode is closed or waitlisted still receives the person on the sign-up page, which refuses there as it always has — so the parameter never becomes a way to read your tenant's configuration. The sign-up page's own "Sign in" link takes the person to sign-in with the same request intact. `prompt_values_supported` in both discovery documents lists the values this authorization server honours. The [application configuration guide](https://www.udibo.com/docs/identity/applications#request-fresh-authentication) describes each one; any other value is refused with `invalid_request`. ## Next steps | Your next goal | Guide | | ------------------------------------------ | ------------------------------------------------------------------------ | | Choose sign-in methods, sign-up, and MFA | [Sign-in methods](https://www.udibo.com/docs/identity/sign-in-methods) | | Put your name and logo on the hosted pages | [Brand your hosted pages](https://www.udibo.com/docs/identity/branding) | | Define sign-out and device controls | [Sessions and sign-out](https://www.udibo.com/docs/identity/sessions) | | Give customers their own workspace | [Organizations](https://www.udibo.com/docs/identity/organizations) | | Control access to features or records | [Permissions and roles](https://www.udibo.com/docs/identity/permissions) | | Prepare a launch | [Production checklist](https://www.udibo.com/docs/identity/production) | _Last verified 2026-10-28._