External sign-in

Hand an HTTPS authorization URL to the device and receive the OAuth callback in your page.

Start a sign-in

Pass bdk.auth.signIn an HTTPS authorization URL you already built — client id, scope, state, PKCE challenge, and normally redirect_uri. The SDK never builds that URL. With handoff: "page", the resolved result is the validated callbackUrl plus decoded query params.

This feature can be switched off in a given app build — check await bdk.capabilities.has("auth.oauth") before showing the UI. See Detect features. When it's off, the call resolves ok: false with code: "common/feature_disabled".

PropertyTypeDescription
urlstring · requiredFull HTTPS authorization URL. You build it.
redirectUristringCallback URI when it isn't already on url.
handoffstringpage returns the callback here. webview opens it in the webview.
preferEphemeralbooleanRequest an ephemeral iOS session when supported.
timeoutMsnumberReturn-trip timeout in milliseconds. Native reads 60000–3600000.
import { createBdkNative } from "@bdk/native/browser";

const bdk = createBdkNative();

const authorizeUrl = new URL("https://idp.example.com/authorize");
authorizeUrl.searchParams.set("client_id", clientId);
authorizeUrl.searchParams.set("redirect_uri", "com.example.app://oauth/return");
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("scope", "openid email");
authorizeUrl.searchParams.set("state", state);
authorizeUrl.searchParams.set("code_challenge", challenge);
authorizeUrl.searchParams.set("code_challenge_method", "S256");

if (!(await bdk.capabilities.has("auth.oauth"))) {
  window.location.href = authorizeUrl.toString();
} else {
  const result = await bdk.auth.signIn({
    url: authorizeUrl.toString(),
    handoff: "page"
  });

  if (!result.ok) {
    console.warn(result.code, result.message); // e.g. common/feature_disabled
    return;
  }

  console.log(result.callbackUrl, result.params.code);
}

Hand off to the webview

Use handoff: "webview" to load the callback in the app webview. The resolved loaded: true only means that page opened — subscribe to auth.completed and auth.cancelled before you call, and treat those events as the outcome.

bdk.on("auth.completed", ({ mode, provider, handoff }) => {
  console.log(mode, provider, handoff);
});

bdk.on("auth.cancelled", ({ reason }) => {
  console.log(reason); // dismissed | provider_denied | timeout
});

const result = await bdk.auth.signIn({
  url: authorizeUrl,
  handoff: "webview"
});

if (!result.ok) {
  console.warn(result.code, result.message);
  return;
}

// result.loaded === true means the page opened, not that sign-in finished.

Handle the callback

Set redirectUri (or redirect_uri on the URL you build) to a callback within your app's registered scheme. Fragment-only OAuth responses are not supported.

CodeMeaning
auth/redirect_unroutableThe callback URI cannot be routed back into this app.
auth/in_progressA sign-in is already running.
auth/callback_mismatchThe callback did not match this sign-in session.
auth/provider_deniedThe provider or user denied the request.
auth/session_failedThe sign-in session failed to complete.

A custom-scheme callback is not authoritative for identity — finish the session server-side.

const result = await bdk.auth.signIn({
  url: "https://idp.example.com/authorize?client_id=…&state=…",
  redirectUri: "com.example.app://oauth/return",
  handoff: "page"
});

if (!result.ok) {
  console.warn(result.code, result.message);
}

Listen for the result

auth.completed reports { mode, provider, handoff } when the session finishes. auth.cancelled reports { mode, provider, reason }mode is auto or explicit; reason is dismissed, provider_denied, or timeout. Subscribe before you call.

const offs = [
  bdk.on("auth.completed", ({ mode, provider, handoff }) => {
    console.log(mode, provider, handoff);
  }),
  bdk.on("auth.cancelled", ({ reason }) => {
    console.log(reason);
  })
];

// later
offs.forEach((off) => off());