Documentation

Touchque Login

Sign-up and sign-in pages for your own customers, on your brand and your domain: passkeys first, the Touchque app or email/SMS codes as the second step, social and enterprise connections, organizations for business customers. The pages carry your logo and colours and always end with a small “Secured by Touchque” line. It is standard OpenID Connect, so any OIDC library works; the SDKs below save you the details.

Five minutes
Endpoints on your sign-in domain
PathWhat
/.well-known/openid-configurationDiscovery
/authorizescreen_hint=signup, login_hint, ui_locales, connection, organization, invitation, prompt, max_age, acr_values
/oauth/tokenauthorization_code (PKCE), refresh_token (rotating), client_credentials, token exchange (RFC 8693), CIBA
/bc-authorizeCIBA (poll mode): an app asks a customer to approve on their phone
/userinfo · /oauth/revoke · /oauth/introspectOpenID Connect and OAuth 2.0
/oidc/logoutid_token_hint, post_logout_redirect_uri
/accountYour customers' own account page
/me/v1My Account API (audience {issuer}/me/, first-party apps)
Always on: PKCE (S256) and state on every sign-in, exact callback URLs, the issuer on the response (RFC 9207), ID tokens checked against the JWKS, refresh tokens that rotate (a reused one ends the whole family), passwords hashed with Argon2id and checked against known breaches.
Attack protection
ProtectionWhat it does
Wrong passwords10 in 15 minutes pause password sign-in for that account; the owner gets an unlock link.
Suspicious addressesMany failures across accounts from one address: the bot challenge, or an hour's wait.
Bot challengeTurnstile, hCaptcha or reCAPTCHA v2, checked on the server with the hostname.
Breached passwordsFound at sign-in: a new password after the second step, or an email.
Adaptive second stepAsked only when a sign-in looks risky (new browser, suspicious address, failed attempts, the risk engine).

Your Actions run on sign-in too: identity.post_login can refuse or ask for a second step, identity.pre_user_registration can refuse a sign-up. Customers manage their own profile, passkeys, devices, app access and data at /account, or in your app through the My Account API and <UserProfile>.

An AI agent acting for a customer

Turn it on for a confidential app (Touchque Login → Setup → Apps → the app → Acting for your customers; your phone approves). With token exchange (RFC 8693) the app trades a signed-in customer's access token for one to call your API: sub is the customer, act.sub the app, the scopes are what the API grants the app (and the customer's roles allow), and it ends with the customer's token; a third app in a chain is refused. With CIBA the app asks the customer on their phone: Touchque draws the authorization_details as rows, shows the app's name first and its message as unverified, asks for Face ID and the number the app shows, and the tokens carry exactly what was approved, once. An approval given during a phone call is not enough.

Token vault. When a connection keeps tokens (Touchque Login → Setup → Connections → the connection → Keep tokens for apps working for people; your phone approves), an app acting for a customer gets that customer's own Google, Microsoft or GitHub access token by token exchange with requested_token_type=urn:touchque:params:oauth:token-type:connection-access-token and connection. Touchque keeps the tokens sealed, refreshes them and never hands out the refresh token; scope may only narrow what the provider granted. The customer's token must be the app's own, unless the connection lists the app. Customers stop it on /account (or stopAppAccess), you with DELETE /identity/users/{id}/connected-accounts/{accountId}; unlinking the account or deleting the customer deletes the tokens.

Sign your users in
No JavaScript example in this block yet; showing Next.js.
// npm init @touchque -- --login   (writes these files)
// lib/touchque.ts
import { createTouchqueLogin } from '@touchque/node';
export const touchque = createTouchqueLogin({
  domain: ({}).TOUCHQUE_LOGIN_DOMAIN!,      // acme.login.touchque.com
  clientId: ({}).TOUCHQUE_LOGIN_CLIENT_ID!,
  clientSecret: ({}).TOUCHQUE_LOGIN_CLIENT_SECRET,
  secret: ({}).TOUCHQUE_SESSION_SECRET!,    // 32+ random characters
  appUrl: ({}).APP_URL!,
});

// app/auth/[action]/route.ts
export const { GET } = touchque.handlers;

// middleware.ts (Edge)
import { touchqueMiddleware } from '@touchque/node/next/middleware';
export default touchqueMiddleware({ secret: ({}).TOUCHQUE_SESSION_SECRET!, protect: ['/dashboard'] });
Management API (your API key)
const { users } = await tq.identity.users.list({ q: 'ayse' });
await tq.identity.users.block(users[0].id, { reason: 'chargeback' });
const job = await tq.identity.imports.start({ users: [{ email: 'old@acme.com', passwordHash: '$2b$10$…' }] });
await tq.identity.setDelivery('email', { provider: 'postmark', from: 'login@acme.com', serverToken });
await tq.identity.setAttackProtection({ bot: { provider: 'turnstile', siteKey, secret, mode: 'risky' } });
const data = await tq.identity.users.export(userId);   // KVKK / GDPR
An AI agent acting for a customer
// An AI agent of yours (a confidential app with these grants on, Touchque Login → Setup → Apps → the app)
const forAda = await login.exchangeToken(adasAccessToken, { audience: 'https://api.acme.com' });   // sub = Ada, act.sub = the app

const ask = await login.askUser({ loginHint: 'ada@acme.com', scope: 'openid payments:create', audience: 'https://api.acme.com',
    bindingMessage: 'Pay invoice 42', authorizationDetails: [payment] });
reply(`Type ${ask.bindingCode} in the Touchque app`);
const approved = await login.waitForUser(ask);   // approved.authorizationDetails

// Token vault: Ada's own Google token (Touchque Login → Setup → Connections → Google → Keep tokens…)
const google = await login.getConnectionToken(adasAccessToken, { connection: 'google' });
await fetch('https://www.googleapis.com/calendar/v3/users/me/calendarList', { headers: { Authorization: `Bearer ${google.accessToken}` } });