IDfier Developers

QR code payload

How to build the QR code and the phone link from a login session, in any language.

The API doesn't return a QR code or an app link: you build both in the browser from the three values of the session (referenceId, qrReferenceSecret, createdAt), and render the QR code again every ~2 seconds.

qrTime     = floor((now - createdAt) / 1000)   // seconds, as a decimal string
key        = SHA256(qrReferenceSecret)         // the raw 32 bytes
qrAuthCode = hex(HMAC_SHA256(key, qrTime))     // lowercase hex
payload    = "idfier.{referenceId}.{qrTime}.{qrAuthCode}"
WhereContent
QR codeidfier://auth?authData=<payload>
Button on a phonehttps://www.idfier.com/auth?authData=<payload> (a universal link that opens the app)

URL-encode the payload in both. IDfier accepts a qrTime within about 10 seconds of its own clock, which is why the code has to be refreshed.

TypeScript (browser)

type Session = { referenceId: string; qrReferenceSecret: string; createdAt: string };

export async function qrPayload(session: Session) {
  const qrTime = String(Math.floor((Date.now() - new Date(session.createdAt).getTime()) / 1000));
  const encoder = new TextEncoder();
  const keyBytes = await crypto.subtle.digest('SHA-256', encoder.encode(session.qrReferenceSecret));
  const key = await crypto.subtle.importKey('raw', keyBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
  const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder.encode(qrTime)));
  const code = Array.from(mac, (byte) => byte.toString(16).padStart(2, '0')).join('');
  return `idfier.${session.referenceId}.${qrTime}.${code}`;
}

crypto.subtle needs a secure context: https, or localhost in development.

Python

import hashlib, hmac, math, time
from datetime import datetime

def qr_payload(reference_id: str, qr_reference_secret: str, created_at: str) -> str:
    created = datetime.fromisoformat(created_at.replace("Z", "+00:00")).timestamp()
    qr_time = str(math.floor(time.time() - created))
    key = hashlib.sha256(qr_reference_secret.encode()).digest()
    code = hmac.new(key, qr_time.encode(), hashlib.sha256).hexdigest()
    return f"idfier.{reference_id}.{qr_time}.{code}"

Keep qrReferenceSecret out of logs

The browser needs it to draw the QR code, but don't log or store it: with it, anyone can produce a valid code for the session.

On this page