Sign in with Configure
Configure can provide authentication for your app. The user signs in through the hosted flow, and your server receives a user token. Use that token to access the profile instead of externalId. Configure manages password-free sign-in and phone or email verification. The button can display your app's name or a label such as "Connect your context".
Use this integration when your app has no existing user accounts. Use the user token for profile access. Follow the Quickstart for profile refreshes, conversation commits, and tools.
If your app already has user accounts, use the onboarding integration to connect each existing user ID to Configure. This preserves your current account system.
Setup already created what this needs: CONFIGURE_PUBLISHABLE_KEY (pk_ prefix, safe in the browser) and your secret key. Configure only sends a signed-in user back to a callback you registered for your agent. You register it once at boot, in server.ts below.
The client is the same one the rest of the integration uses:
ts
import { Configure } from "configure";
export const configure = new Configure({
apiKey: process.env.CONFIGURE_API_KEY, // sk_..., server only
agent: process.env.CONFIGURE_AGENT, // your agent handle
});
export const profileFor = (userId: string) => configure.profile({ externalId: userId });ts
import { randomUUID } from "node:crypto";
const RETURN_TO = "https://yourapp.com/callback"; // a constant
await configure.auth.allowSignInReturnTo(RETURN_TO); // once at boot; idempotent
// GET /login
app.get("/login", (req, res) => {
const state = randomUUID();
res.cookie("configure_state", state, { httpOnly: true, sameSite: "lax", maxAge: 600_000 });
res.redirect(
configure.auth.signInUrl({
publishableKey: process.env.CONFIGURE_PUBLISHABLE_KEY,
returnTo: RETURN_TO,
state,
displayName: "Your App",
}),
);
});
// GET /callback
app.get("/callback", async (req, res) => {
const expected = req.cookies.configure_state;
res.clearCookie("configure_state"); // one attempt per state, pass or fail
if (!req.query.state || req.query.state !== expected) {
return res.status(400).send("Invalid sign-in state.");
}
const { token, userId } = await configure.auth.exchangeSignInCode(String(req.query.code));
await sessions.set(req, { userId, token }); // this user's key to their profile
res.redirect("/");
});Apply these requirements to redirect sign-in:
stateis a one-time value bound to the browser. Create it per attempt, keep it in anhttpOnlycookie, compare on return, and clear it. That is the CSRF protection for the callback.- The code is single use. Exchange it once, server-side, with your secret key. A refresh or the Back button replays the URL, and the second exchange fails. Treat that failure as "already signed in", not as an error to retry.
returnTois a constant. Never build it from the request. Configure refuses a destination you did not register. An app that forwards to a URL from a query parameter is an open redirector.
Need a verified email? Use OpenID Connect with the email scope. The ID token then carries email and email_verified. required=gmail|outlook on the authorize request puts the mailbox page first. What the hosted flow asks for lists every screen and its order for each way in.
The token is an agent credential for this user, valid for 24 hours. Keep it in the session. Use configure.profile({ token }) in place of configure.profile({ externalId }) for the daily refresh, the first-turn injection, the commit, and the tools. When the token expires, send the user through /login again.
To do all of this in one call, use personalize() from the same package. It wires /login, /callback, and the boot registration for you:
ts
import { personalize } from "configure";
personalize({
apiKey: process.env.CONFIGURE_API_KEY,
publishableKey: process.env.CONFIGURE_PUBLISHABLE_KEY,
agent: process.env.CONFIGURE_AGENT,
baseUrl: "https://yourapp.com", // callback becomes baseUrl + "/callback"
onSignedIn: ({ userId, token, profile }) => sessions.create({ userId, token }),
}).listen(3000);There are three separate registrations:
allowSignInReturnToregisters where a sign-in may return.npx configure add originregisters your OAuth client's callback.allowed_return_hostson your developer account is the only one the connect return consults. It is required when the browser does not send a matchingRefererorigin.
The drop-in button
Use the Configure sign-in button to launch the authorization flow without implementing a custom button.
html
<script src="https://configure.dev/js/configure.js"></script>
<configure-sso-button
client-id="oc_your_client_id"
redirect-uri="https://yourapp.com/auth/configure/callback"
agent-name="Your App"
scopes="profile.read profile.search profile.remember profile.commit"
width="100%"
></configure-sso-button>The script mounts the button on load, so you do not call anything. To mount it yourself from JavaScript, use Configure.ssoButton({ el, clientId, redirectUri, label, connectedLabel, connected, size, width, theme, font, onEvent }). data-configure-sso on a plain element also mounts it. If you leave out clientId or redirectUri, the button renders a setup notice instead of a broken control.
This button uses a different registration from the redirect flow above. client-id is your OAuth client, with the oc_ prefix. redirect-uri is a callback registered on that client with npx configure add origin <full callback URL>. allowSignInReturnTo does not register this OAuth callback.
The button opens Configure in a popup and returns an OAuth authorization code, not a token. It emits configure:sso-start, configure:sso-open, configure:sso-authenticated, configure:sso-error, and configure:sso-close. Your callback page reads the PKCE verifier the button stored. It posts the code to your own server to exchange it. Then it tells the opener the result:
html
<script src="https://configure.dev/js/configure.js"></script>
<script type="module">
const params = new URLSearchParams(location.search);
const state = params.get("state");
const pkce = Configure.getSsoPkce(state); // stored by the button, per state
if (params.get("error") || !pkce?.codeVerifier) {
Configure.completeSso({ error: params.get("error") || "pkce_missing", payload: {} });
} else {
const res = await fetch("/auth/configure/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: params.get("code"), codeVerifier: pkce.codeVerifier, state }),
});
Configure.completeSso(res.ok ? { state, payload: await res.json() } : { error: "exchange_failed", payload: {} });
}
</script>completeSso() posts the result to the opener, clears the stored verifier, and closes the window. The exchange stays on your server with your secret key, as in the redirect flow. The code is single use, and a browser must never hold the credential that creates tokens.
If the authorization request omits scope, Configure uses the scopes registered for that client. A client made with POST /v1/sso/client registers openid, email, and profile. If the account has no proven mailbox, its sign-in asks for one after the code. The OpenID Connect page covers scopes and claims.
Sign in without leaving the page
Configure.signInWithPopup() opens the same sign-in in a popup and passes a sign-in code back to your page. Your server exchanges it the same way /callback does. The user never leaves your page.
html
<script src="https://configure.dev/js/configure.js"></script>
<script>
document.getElementById("sign-in").addEventListener("click", () => {
const state = crypto.randomUUID();
Configure.signInWithPopup({
publishableKey: "pk_...",
agent: "your-agent",
returnTo: "https://yourapp.com/callback", // registered with allowSignInReturnTo, as above
state,
onEvent: async (event) => {
if (event.type === "configure:sign_in_code" && event.payload.state === state) {
await fetch("/auth/configure/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: event.payload.code }),
});
location.reload();
}
if (event.type === "configure:error") showSignInFailed(event.payload.message); // popup_blocked
},
});
});
</script>publishableKey, agent, and returnTo are required. If one is missing, the call throws. The page emits two events:
configure:sign_in_codewith{ code, state, returnTo, agent }.configure:errorwhen the browser blocks the popup.
Pass fallback: "redirect" to send the whole page to the sign-in when the popup is blocked. popupName and popupFeatures set the window.
ts
// POST /auth/configure/exchange: the same exchange as /callback, with your secret key.
app.post("/auth/configure/exchange", async (req, res) => {
const { token, userId } = await configure.auth.exchangeSignInCode(String(req.body.code));
await sessions.set(req, { userId, token });
res.sendStatus(204);
});The code is single use. The exchange stays on your server, as in the redirect flow, because a browser must never hold the credential that creates tokens.
Choose the authentication integration
Sign in with Configure replaces your account system. If you already have one and want Configure as a sign-in option inside it, use OpenID Connect. If you need context without adding a sign-in provider, use the onboarding integration with your existing user IDs. Follow the Quickstart for context reads, conversation commits, and tool access.