Website sign-in
Your login page shows a QR code. The person scans it with the AllowID app, sees your company's name and what you are asking for, and approves. Your server learns who signed in.
1. Your server starts a session§
On the request for your login page, create a session and keep its id in your own session for this browser (a cookie, your framework's session). Then put three values on the page.
# Flask
@app.get("/login")
def login():
s = allowid_client.create_session("Sign in to Example Co", ["profession"])
session["allowid"] = s.session_id
return render_template("login.html", s=s)
// Express
app.get("/login", async (req, res) => {
const s = await allowid.createSession({ purpose: "Sign in to Example Co", attributes: ["profession"] });
req.session.allowid = s.sessionId;
res.render("login", { s });
});
// ASP.NET Core
app.MapGet("/login", async (HttpContext ctx, AllowIDClient allowid) =>
{
var s = await allowid.CreateSessionAsync("Sign in to Example Co", new[] { "profession" });
ctx.Session.SetString("allowid", s.SessionId);
return Results.Content(LoginPage(s), "text/html");
});
// Any servlet framework
AllowID.Session s = allowid.createSession("Sign in to Example Co", List.of("profession"));
request.getSession().setAttribute("allowid", s.sessionId());
// render the login page with s.sessionId(), s.browserToken(), s.qrPayload()
session_start();
$s = $allowid->createSession('Sign in to Example Co', ['profession']);
$_SESSION['allowid'] = $s['session_id'];
// render the login page with $s
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
s, err := client.CreateSession(r.Context(), "Sign in to Example Co", []string{"profession"})
if err != nil {
http.Error(w, "sign-in unavailable", http.StatusBadGateway)
return
}
setSessionValue(w, r, "allowid", s.SessionID) // your session store
loginPage.Execute(w, s)
})purpose is shown to the person in the app. Keep it short and specific. The optional
attributes are things the person may choose to share besides their email:
profession, company, address, age_band,
nationality. Ask only for what you use; each one is off until the person turns it on.
2. The page shows the QR§
Download allowid-login.js (17 KB, no dependencies) and
serve it from your own site. Then one element and one script tag do the rest:
<div data-allowid-session="{{ s.session_id }}"
data-allowid-token="{{ s.browser_token }}"
data-allowid-qr="{{ s.qr_payload }}"
data-allowid-redirect="/login/done"></div>
<script src="/js/allowid-login.js" defer></script>
Escape the values as you would any attribute (your template engine does). The QR payload contains
quotes. When the person approves, the browser goes to /login/done. Options, events and
styling are in The sign-in widget.
3. Your server collects the result§
@app.get("/login/done")
def login_done():
r = allowid_client.get_session(session.pop("allowid", ""))
if not r.authenticated:
return redirect("/login")
user = users.find_or_create(subject=r.subject, email=r.claims["email"])
login_user(user)
return redirect("/")
app.get("/login/done", async (req, res) => {
const r = await allowid.getSession(req.session.allowid);
delete req.session.allowid;
if (r.state !== "authenticated") return res.redirect("/login");
req.session.user = await users.findOrCreate({ subject: r.subject, email: r.claims.email });
res.redirect("/");
});
app.MapGet("/login/done", async (HttpContext ctx, AllowIDClient allowid) =>
{
var id = ctx.Session.GetString("allowid");
ctx.Session.Remove("allowid");
var r = await allowid.GetSessionAsync(id ?? "");
if (!r.Authenticated) return Results.Redirect("/login");
await SignIn(ctx, r.Subject!, r.Claims["email"]);
return Results.Redirect("/");
});
String id = (String) request.getSession().getAttribute("allowid");
request.getSession().removeAttribute("allowid");
AllowID.SessionResult r = allowid.getSession(id);
if (!r.authenticated()) { response.sendRedirect("/login"); return; }
signIn(request, r.subject(), r.claims().get("email"));
response.sendRedirect("/");
session_start();
$r = $allowid->getSession($_SESSION['allowid'] ?? '');
unset($_SESSION['allowid']);
if ($r['state'] !== 'authenticated') { header('Location: /login'); exit; }
sign_in($r['subject'], $r['claims']['email']);
header('Location: /');
http.HandleFunc("/login/done", func(w http.ResponseWriter, r *http.Request) {
res, err := client.GetSession(r.Context(), takeSessionValue(w, r, "allowid"))
if err != nil || !res.Authenticated() {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
signIn(w, r, res.Subject, res.Claims["email"])
http.Redirect(w, r, "/", http.StatusSeeOther)
})state is pending, authenticated, denied (the person declined in the app) or
expired. The first get_session that returns authenticated is billed; asking again is free.
Who signed in§
subject: use this as the person's key in your user table. It never changes for this person at your company, and the same person shows the same subject when they scan a QR reader at your premises (any QR reader).claims.emailis always there after a sign-in. It is the address the person signed up to AllowID with; they verified it at the time. People change addresses, so match on the subject.- Other claims appear only if you asked for them and the person agreed.
Timing§
- The QR code is valid for three minutes. After that the widget says so and offers a new code, which reloads your page to get a new session.
- The result can be collected until ten minutes after the QR expired.
- The widget checks the state once a second, with the browser token. Those checks are free.
Without the widget§
If you'd rather draw the QR yourself, render qr_payload with any QR library (error
correction M is fine) and poll GET /v1/sessions/{id}/status with the header
X-AllowID-Browser-Token, or poll get_session from your server. Native apps and kiosks
can use wait_for_session, which polls until the person has decided.
A complete example§
The JavaScript SDK has a whole working sign-in site in one file:
examples/website/server.mjs in the JavaScript download.
Run it with your key and sign in with your phone.