authentik-qr-login: cross-device QR sign-in for authentik

Scan a QR on your authentik login page with your phone, approve it with a
passkey and a fingerprint, and the laptop signs itself in.

The service holds NO authentik credential. A flow policy calls it with a
session id and gets back the username that approved it, or nothing -- so there
is no standing credential to steal. The obvious alternative, authentik's
recovery-link API, effectively requires a superuser and was rejected for that
reason.

See README.md for the traps this had to work around, including the two
flow-binding settings that are counter-intuitive and load-bearing, and an
honest account of what QR sign-in cannot defend against.
This commit is contained in:
2026-08-16 17:20:57 +01:00
commit 44061918ac
16 changed files with 2965 additions and 0 deletions

38
Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
# authentik-qr-login. Build context is services/authentik-qr-login.
#
# Registries are fully qualified: podman enforces short-name resolution and
# fails a non-interactive build with "cannot prompt without a TTY" rather than
# choosing one.
FROM docker.io/library/node:22-alpine AS build
WORKDIR /src
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json tsconfig.test.json ./
COPY src ./src
COPY test ./test
# Typecheck and test INSIDE the image build. The service is outside the root
# tsconfig's include, so nothing else would check it — and an image that
# compiles but fails its own tests should not reach a registry.
RUN npm run typecheck
RUN npm test
RUN npm run build
# Drop devDependencies from what gets copied forward.
RUN npm prune --omit=dev
FROM docker.io/library/node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /src/node_modules ./node_modules
COPY --from=build /src/dist ./dist
COPY --from=build /src/package.json ./package.json
# The node image ships a `node` user at uid 1000; the Deployment pins the same
# uid with a read-only root filesystem.
USER node
EXPOSE 8080
CMD ["node", "dist/index.js"]