Integration code
Add the code below to your backend project as a single file. All Touchque communication runs through this file. Choose your preferred language.
This file contains 2 sections:
1) Protect a route with the requireTouchque middleware — it answers 202 until approved
2) Read the approval off req.touchque once the handler runs
1) Protect a route with the requireTouchque middleware — it answers 202 until approved
2) Read the approval off req.touchque once the handler runs
Endpoint
| Path | Function |
|---|---|
POST /api/login | Protected by requireTouchque('LOGIN') |
POST /api/transfer | Protected by requireTouchque('SEND_MONEY') |
The approval is bound to one user + action + transaction and can be consumed exactly once — a token can't be replayed for a different amount, recipient or user.
Express integration file
const express = require('express');
const { requireTouchque } = require('@touchque/node');
const app = express();
app.use(express.json());
// ─── TQ_API_KEY / TQ_API_SECRET are read from the environment ───
// ═══════════════════════════════════════
// 1) Sensitive Routes — One-Line Middleware!
// ═══════════════════════════════════════
// Request 2FA approval for login
app.post(
'/api/login',
requireTouchque('LOGIN', { user: (req) => req.session.passwordOk }),
(req, res) => {
// ✅ Only reached once the user approved on their phone.
res.json({ success: true, assurance: req.touchque.assurance });
}
);
// Request 2FA approval for a money transfer (with transaction details)
app.post(
'/api/transfer',
requireTouchque('SEND_MONEY', {
user: (req) => req.session.user?.email,
details: (req) => ({ Amount: `${req.body.amount} EUR`, To: req.body.iban }),
}),
(req, res) => {
res.json({ success: true, message: 'Transfer completed!' });
}
);
// ═══════════════════════════════════════
// 2) What actually happens under the hood?
// ═══════════════════════════════════════
// a) The first request answers 202 { touchque: step, token } (no approval yet).
// b) Your frontend shows step.number (or step.enroll.qrCodeDataUrl on first link) in
// its own design, then resends the same request with header X-Touchque-Token
// (@touchque/web's touchqueFetch() does this loop for you).
// c) Once approved, the handler above runs EXACTLY ONCE.
app.listen(3000, () => console.log('Server: http://localhost:3000'));<?php
// bootstrap/app.php (Laravel 11+)
$middleware->alias(['touchque' => \Touchque\Laravel\TouchqueMiddleware::class]);
// ─── In a service provider ───
// $this->app->singleton(\Touchque\Touchque::class, fn () => new \Touchque\Touchque());
// (TQ_API_KEY / TQ_API_SECRET are read from the environment)
// ═══════════════════════════════════════
// routes/api.php — Sensitive Routes
// ═══════════════════════════════════════
Route::post('/login', [LoginController::class, 'store'])
->middleware('touchque:LOGIN');
Route::post('/transfer', [TransferController::class, 'store'])
->middleware('touchque:SEND_MONEY');
// TransferController@store:
//
// public function store(Request $request) {
// // Only reached once the user approved on their phone.
// $approval = $request->attributes->get('touchque');
// return response()->json(['success' => true, 'assurance' => $approval['assurance']]);
// }
// The first request answers 202 { touchque: step, token }; once approved,
// the controller runs exactly once — see @touchque/web for the frontend side.package main
import (
"encoding/json"
"log"
"net/http"
"github.com/Touchque/touchque-go/v5/touchque"
)
func main() {
tq, err := touchque.New() // TQ_API_KEY / TQ_API_SECRET from the environment
if err != nil {
log.Fatal(err)
}
// 1) Sensitive Routes — one-line wrapper!
mux := http.NewServeMux()
mux.Handle("/api/login", touchque.Require(tq, "LOGIN", http.HandlerFunc(loginHandler), touchque.RequireOptions{
User: func(r *http.Request) string { return sessionUser(r) }, // the user who passed your password step
}))
mux.Handle("/api/transfer", touchque.Require(tq, "SEND_MONEY", http.HandlerFunc(transferHandler), touchque.RequireOptions{
User: func(r *http.Request) string { return sessionUser(r) },
Details: func(r *http.Request) []touchque.LoginDetail {
return []touchque.LoginDetail{{Label: "Amount", Value: amountFromRequest(r)}}
},
}))
log.Fatal(http.ListenAndServe(":3000", mux))
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
// ✅ Only reached once the user approved on their phone.
approval, _ := touchque.ApprovalFromContext(r.Context())
json.NewEncoder(w).Encode(map[string]interface{}{"success": true, "assurance": approval.Assurance})
}
func transferHandler(w http.ResponseWriter, r *http.Request) {
// Only reached once the user approved on their phone.
approval, _ := touchque.ApprovalFromContext(r.Context())
json.NewEncoder(w).Encode(map[string]interface{}{"success": true, "assurance": approval.Assurance})
}
// sessionUser and amountFromRequest are your own session and request helpers.
// The first request answers 202 { touchque: step, token }; once approved,
// the handler runs exactly once — see @touchque/web for the frontend side.from flask import Flask, g, jsonify, request, session
from touchque.contrib.flask import require_touchque
app = Flask(__name__)
app.secret_key = "change-me" # for the Flask session
# ─── TQ_API_KEY / TQ_API_SECRET are read from the environment ───
# ═══ 1) Sensitive Routes — one-line decorator! ═══
@app.post("/api/login")
@require_touchque("LOGIN", user=lambda: session.get("password_ok"))
def login():
# ✅ Only reached once the user approved on their phone.
return jsonify(success=True, touchque=g.touchque)
@app.post("/api/transfer")
@require_touchque("SEND_MONEY", details=lambda: {"Amount": f'{request.json["amount"]} EUR'})
def transfer():
return jsonify(success=True, message="Transfer completed!")
# The first request answers 202 { touchque: step, token }; once approved,
# the view runs exactly once — see @touchque/web for the frontend side.
if __name__ == "__main__":
app.run(port=3000)using Touchque.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTouchque(); // TQ_API_KEY / TQ_API_SECRET are read from the environment
var app = builder.Build();
// ═══════════════════════════════════════
// 1) Sensitive Routes — one line per endpoint!
// ═══════════════════════════════════════
app.MapPost("/api/login", (HttpContext http) =>
// ✅ Only reached once the user approved on their phone.
Results.Ok(new { success = true, assurance = http.GetTouchqueApproval()!.Assurance }))
.RequireTouchque("LOGIN", o => o.User = c => c.HttpContext.Session.GetString("password_ok"));
app.MapPost("/api/transfer", (Transfer t) => Results.Ok(new { success = true, message = "Transfer completed!" }))
.RequireAuthorization()
.RequireTouchque("SEND_MONEY", o => o.Details = c =>
[("Amount", $"{c.Argument<Transfer>()!.Amount} EUR"), ("To", c.Argument<Transfer>()!.Iban)]);
// The first request answers 202 { touchque: step, token }; once approved,
// the endpoint runs exactly once — see @touchque/web for the frontend side.
// Controllers: [RequireTouchque("SEND_MONEY")]
app.Run();
record Transfer(decimal Amount, string Iban);// build.gradle.kts: implementation("com.touchque:touchque-spring-boot-starter:5.2.0") (Maven Central)
// TQ_API_KEY / TQ_API_SECRET are read from the environment (or touchque.api-key / touchque.api-secret)
// ═══════════════════════════════════════
// 1) Sensitive Routes — one line per method!
// ═══════════════════════════════════════
@RestController
class Api {
@PostMapping("/api/login")
@RequireTouchque("LOGIN")
Map<String, Object> login(Approval approval) {
// ✅ Only reached once the user approved on their phone.
return Map.of("success", true, "assurance", approval.assurance());
}
@PostMapping("/api/transfer")
@RequireTouchque("SEND_MONEY")
Map<String, Object> transfer(@RequestBody Transfer t) {
return Map.of("success", true, "message", "Transfer completed!");
}
}
record Transfer(BigDecimal amount, String iban) {}
// What the phone shows (and who approves the login step):
@Bean
TouchqueGuardOptions loginGuard() {
return TouchqueGuardOptions.forAction("LOGIN")
.user(c -> (String) c.request().getSession().getAttribute("password_ok"));
}
@Bean
TouchqueGuardOptions sendMoneyGuard() {
return TouchqueGuardOptions.forAction("SEND_MONEY").details(c -> List.of(
LoginDetail.of("Amount", c.argument(Transfer.class).amount() + " EUR"),
LoginDetail.of("To", c.argument(Transfer.class).iban())));
}
// The first request answers 202 { touchque: step, token }; once approved,
// the method runs exactly once — see @touchque/web for the frontend side.# Gemfile: gem "touchque", "~> 5.0" (RubyGems)
# TQ_API_KEY / TQ_API_SECRET are read from the environment (or credentials.touchque)
# ═══ 1) Sensitive Routes — one line per action! ═══
class SessionsController < ApplicationController
include Touchque::Rails::Guard
require_touchque "LOGIN", only: :create, user: -> { session[:password_ok] }
def create
# ✅ Only reached once the user approved on their phone.
render json: { success: true, assurance: touchque_approval["assurance"] }
end
end
class TransfersController < ApplicationController
include Touchque::Rails::Guard
require_touchque "SEND_MONEY", only: :create,
details: -> { { "Amount" => "#{params.require(:amount)} EUR", "To" => params.require(:iban) } }
def create
render json: { success: true, message: "Transfer completed!" }
end
end
# The first request answers 202 { touchque: step, token }; once approved,
# the action runs exactly once — see @touchque/web for the frontend side.