Relay routes
Mount one router and get every route the browser SDK (@touchque/web) calls: two-phase POST /login, enrollment, offline codes and passkey ceremonies. Available in every server SDK: Node (touchqueRouter), Python (Flask / FastAPI / Django), PHP (Laravel, or RelayHandler on its own), Go (net/http), .NET (MapTouchqueRelay), Java (the Spring Boot starter) and Ruby (Touchque::Rack::Relay for Rails and Rack).
Always set the login user option (the user who passed your password step). Without it the username comes from the request body, and the offline routes stay off (403).
| Route | Notes |
|---|---|
POST /login | 202 + step + token until approved |
/enroll/start, /enroll/status | Signed-in user; never replaces a linked phone (409) |
/offline/* | Only with the login user option |
/passkey/* | Passwordless sign-in, approve with passkey, manage passkeys |
Pre-auth routes are rate limited per IP (20/min) and per username (5/min) out of the box.
Mount the relay
const { Touchque, touchqueRouter } = require('@touchque/node');
const tq = new Touchque(); // TQ_API_KEY / TQ_API_SECRET
app.use('/touchque', touchqueRouter(tq, {
getUserId: (req) => req.session.user, // signed-in user
getLoginUser: (req) => req.session.passwordVerifiedUser, // user who passed your password step
onAuthenticated: (req, res, info) => { req.session.user = info.externalUsername; },
}));# Flask (FastAPI: touchque_relay_router, Django: touchque_relay_urls)
from touchque import Touchque
from touchque.contrib.flask import touchque_relay_blueprint
tq = Touchque() # TQ_API_KEY / TQ_API_SECRET
def opened(request, response, info):
session['user'] = info['externalUsername']
app.register_blueprint(touchque_relay_blueprint(
tq,
get_user_id=lambda req: session.get('user'),
get_login_user=lambda req: session.get('password_ok'),
on_authenticated=opened,
), url_prefix='/touchque')// Laravel — a service provider
use Illuminate\Http\Request;
use Touchque\Laravel\TouchqueRelay;
use Touchque\Relay\RelayResponse;
$this->app->singleton(TouchqueRelay::class, fn ($app) => new TouchqueRelay($app->make(\Touchque\Touchque::class), [
'getLoginUser' => fn (Request $r) => $r->session()->get('password_verified_user'),
'onAuthenticated' => function (Request $r, RelayResponse $res, array $info) {
$r->session()->regenerate();
$r->session()->put('user', $info['externalUsername']);
},
]));
// routes/web.php
TouchqueRelay::routes(app('router'), 'touchque');tq, err := touchque.New() // TQ_API_KEY / TQ_API_SECRET
if err != nil {
log.Fatal(err)
}
mux.Handle("/touchque/", http.StripPrefix("/touchque", touchque.NewRelayHandler(tq, touchque.RelayOptions{
UserID: func(r *http.Request) string { return sessionUser(r) }, // required
LoginUser: func(r *http.Request) string { return passwordVerifiedUser(r) },
OnAuthenticated: func(w http.ResponseWriter, r *http.Request, info touchque.AuthenticatedInfo) error {
return startSession(w, info.ExternalUsername)
},
})))builder.Services.AddTouchque(); // TQ_API_KEY / TQ_API_SECRET
app.MapTouchqueRelay("/touchque", new RelayOptions<HttpContext>
{
GetUserId = http => http.User.TouchqueUser(), // signed-in user
GetLoginUser = http => http.Session.GetString("password_verified_user"), // user who passed your password step
OnAuthenticated = (http, response, info) => SignInAsync(http, info.ExternalUsername),
});// The Spring Boot starter serves the routes under /touchque (touchque.relay.path)
@Bean
RelayOptions<HttpServletRequest> touchqueRelay() {
return new RelayOptions<HttpServletRequest>(TouchqueSpring::user) // signed-in user
.getLoginUser(req -> (String) req.getSession().getAttribute("password_verified_user")) // user who passed your password step
.onAuthenticated((req, response, info) -> signIn(req, info.externalUsername()));
}# config/routes.rb
mount Touchque::Rack::Relay.new(
get_user_id: ->(req) { req.session["user"] }, # signed-in user
get_login_user: ->(req) { req.session["password_verified_user"] }, # user who passed your password step
on_authenticated: ->(req, _response, info) { req.session["user"] = info["externalUsername"] }
) => "/touchque"