diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6337909 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space + +[*.{md,yml,yaml,css,json}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..3b0a865 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1,7 @@ +# GitBook serves the domain until it points to GitHub Pages. The pages moved to docs/ for MkDocs, and this +# tells GitBook where they are. It goes away with the GitBook space. +root: ./docs/ + +structure: + readme: README.md + summary: SUMMARY.md diff --git a/.gitbook/assets/fido2 (1).jpeg b/.gitbook/assets/fido2 (1).jpeg deleted file mode 100644 index 64b2450..0000000 Binary files a/.gitbook/assets/fido2 (1).jpeg and /dev/null differ diff --git a/.gitbook/assets/images.jpeg b/.gitbook/assets/images.jpeg deleted file mode 100644 index 10ad08d..0000000 Binary files a/.gitbook/assets/images.jpeg and /dev/null differ diff --git a/.gitbook/assets/login (1).png b/.gitbook/assets/login (1).png deleted file mode 100644 index 39ddb60..0000000 Binary files a/.gitbook/assets/login (1).png and /dev/null differ diff --git a/.gitbook/assets/master (1).jpg b/.gitbook/assets/master (1).jpg deleted file mode 100644 index fef008e..0000000 Binary files a/.gitbook/assets/master (1).jpg and /dev/null differ diff --git a/.gitbook/assets/registration (1).png b/.gitbook/assets/registration (1).png deleted file mode 100644 index 95bd5e2..0000000 Binary files a/.gitbook/assets/registration (1).png and /dev/null differ diff --git a/.gitbook/assets/registration.png b/.gitbook/assets/registration.png deleted file mode 100644 index 95bd5e2..0000000 Binary files a/.gitbook/assets/registration.png and /dev/null differ diff --git a/.gitbook/assets/webauthn-android-fennec.png b/.gitbook/assets/webauthn-android-fennec.png deleted file mode 100644 index 8645abb..0000000 Binary files a/.gitbook/assets/webauthn-android-fennec.png and /dev/null differ diff --git a/.github/workflows/auto-switch-default-branch.yml b/.github/workflows/auto-switch-default-branch.yml deleted file mode 100644 index 98c211a..0000000 --- a/.github/workflows/auto-switch-default-branch.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Auto-switch default branch on new branch - -# Se déclenche lorsqu'une branche est créée -on: - create: - -jobs: - update-default-branch: - # On ne traite que les créations de branches (pas les tags) - if: github.event.ref_type == 'branch' - runs-on: ubuntu-latest - permissions: - contents: write # nécessaire pour modifier le repo via l'API - - steps: - - name: Extraire la version depuis le nom de la branche - id: vars - run: | - ref="${GITHUB_REF#refs/heads/}" # ex: 1.2, v1.2, 1.2.x, v1.2.x - echo "Branch ref: $ref" - - # On enlève un éventuel préfixe 'v' (v1.2 -> 1.2, v1.2.x -> 1.2.x) - version="${ref#v}" - - # On enlève un éventuel suffixe '.x' (1.2.x -> 1.2) - version="${version%.x}" - - echo "Version nettoyée: $version" - - # Vérifier que c'est bien au format major.minor - if [[ ! "$version" =~ ^([0-9]+)\.([0-9]+)$ ]]; then - echo "Le nom de branche '$ref' n'est pas un format de version reconnu, on ignore." - echo "valid=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - major="${BASH_REMATCH[1]}" - minor="${BASH_REMATCH[2]}" - - echo "Major: $major" - echo "Minor: $minor" - - echo "valid=true" >> "$GITHUB_OUTPUT" - echo "major=$major" >> "$GITHUB_OUTPUT" - echo "minor=$minor" >> "$GITHUB_OUTPUT" - echo "new_branch=$ref" >> "$GITHUB_OUTPUT" - - - name: Récupérer la branche par défaut actuelle - if: steps.vars.outputs.valid == 'true' - id: repo - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - default_branch=$(gh repo view "${GITHUB_REPOSITORY}" --json defaultBranchRef --jq .defaultBranchRef.name) - echo "Branche par défaut actuelle: $default_branch" - echo "default_branch=$default_branch" >> "$GITHUB_OUTPUT" - - - name: Décider s'il faut basculer - if: steps.vars.outputs.valid == 'true' - id: decide - run: | - new_branch="${{ steps.vars.outputs.new_branch }}" - current="${{ steps.repo.outputs.default_branch }}" - major="${{ steps.vars.outputs.major }}" - minor="${{ steps.vars.outputs.minor }}" - - echo "Branche par défaut actuelle: $current" - echo "Nouvelle branche: $new_branch" - echo "Version de la nouvelle branche: $major.$minor" - - # Nettoyer le nom de la branche par défaut actuelle pour en extraire la version - current_clean="${current#v}" - current_clean="${current_clean%.x}" - - if [[ "$current_clean" =~ ^([0-9]+)\.([0-9]+)$ ]]; then - current_major="${BASH_REMATCH[1]}" - current_minor="${BASH_REMATCH[2]}" - echo "Version actuelle parsée: $current_major.$current_minor" - else - echo "La branche par défaut actuelle '$current' n'a pas un format de version reconnu, on ignore." - echo "switch_needed=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Comparaison (major, minor) - if [ "$major" -gt "$current_major" ] || { [ "$major" -eq "$current_major" ] && [ "$minor" -gt "$current_minor" ]; }; then - echo "La nouvelle version $major.$minor est supérieure à $current_major.$current_minor → on bascule." - echo "switch_needed=true" >> "$GITHUB_OUTPUT" - else - echo "La nouvelle version $major.$minor n'est pas supérieure à $current_major.$current_minor → pas de changement." - echo "switch_needed=false" >> "$GITHUB_OUTPUT" - fi - - - name: Mettre à jour la branche par défaut - if: steps.vars.outputs.valid == 'true' && steps.decide.outputs.switch_needed == 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - new_branch="${{ steps.vars.outputs.new_branch }}" - echo "Basculement de la branche par défaut vers: $new_branch" - - gh api -X PATCH "repos/${GITHUB_REPOSITORY}" \ - -f default_branch="$new_branch" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..aab53ac --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,38 @@ +name: Check + +on: + pull_request: + workflow_dispatch: + +# MkDocs 2.0 is announced as backward incompatible with every plugin and theme, so the toolchain is pinned +# to the 1.x line in requirements.txt and the two notices about it are silenced: they are news about the +# ecosystem, not findings about this site. +env: + NO_MKDOCS_2_WARNING: 'true' + DISABLE_MKDOCS_2_WARNING: 'true' + +permissions: + contents: read + +jobs: + build: + name: Strict build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + cache: pip + cache-dependency-path: requirements.txt + + - name: Install the toolchain + run: pip install -r requirements.txt + + # --strict turns every warning into an error: a broken internal link, an anchor that does not + # exist, a page absent from the navigation or a page in the navigation that does not exist. + - name: Build + run: mkdocs build --strict diff --git a/.github/workflows/default-branch.yml b/.github/workflows/default-branch.yml new file mode 100644 index 0000000..fdee05b --- /dev/null +++ b/.github/workflows/default-branch.yml @@ -0,0 +1,73 @@ +name: Default branch + +# A new version branch becomes the default branch of the repository, so that a clone and the "edit this +# page" links land on the newest documentation. Changing the default branch is an administration +# permission, which the token of Actions cannot be granted: it needs a personal access token with the +# "repo" scope, in the DEFAULT_BRANCH_TOKEN secret. Without it the workflow does nothing. +on: + create: + workflow_dispatch: + +jobs: + switch: + if: github.event_name == 'workflow_dispatch' || github.event.ref_type == 'branch' + runs-on: ubuntu-latest + permissions: + contents: read + env: + GH_TOKEN: ${{ secrets.DEFAULT_BRANCH_TOKEN }} + steps: + - name: Is the token available? + id: token + run: | + if [ -z "${GH_TOKEN}" ]; then + echo "DEFAULT_BRANCH_TOKEN is not set, nothing to do." + echo "available=false" >> "$GITHUB_OUTPUT" + else + echo "available=true" >> "$GITHUB_OUTPUT" + fi + + - name: The version of the branch that was created + id: created + if: steps.token.outputs.available == 'true' + run: | + ref="${GITHUB_REF#refs/heads/}" + version="${ref#v}" + version="${version%.x}" + if [[ ! "$version" =~ ^([0-9]+)\.([0-9]+)$ ]]; then + echo "The branch '$ref' is not a version, nothing to do." + echo "valid=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "valid=true" >> "$GITHUB_OUTPUT" + echo "branch=$ref" >> "$GITHUB_OUTPUT" + echo "major=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT" + echo "minor=${BASH_REMATCH[2]}" >> "$GITHUB_OUTPUT" + + - name: Is it newer than the default branch? + id: decide + if: steps.created.outputs.valid == 'true' + run: | + current=$(gh repo view "${GITHUB_REPOSITORY}" --json defaultBranchRef --jq .defaultBranchRef.name) + clean="${current#v}" + clean="${clean%.x}" + if [[ ! "$clean" =~ ^([0-9]+)\.([0-9]+)$ ]]; then + echo "The current default branch '$current' is not a version, nothing to do." + echo "switch=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + major="${{ steps.created.outputs.major }}" + minor="${{ steps.created.outputs.minor }}" + if [ "$major" -gt "${BASH_REMATCH[1]}" ] || { [ "$major" -eq "${BASH_REMATCH[1]}" ] && [ "$minor" -gt "${BASH_REMATCH[2]}" ]; }; then + echo "${major}.${minor} is newer than $clean, switching." + echo "switch=true" >> "$GITHUB_OUTPUT" + else + echo "${major}.${minor} is not newer than $clean, nothing to do." + echo "switch=false" >> "$GITHUB_OUTPUT" + fi + + - name: Switch it + if: steps.decide.outputs.switch == 'true' + run: | + gh api -X PATCH "repos/${GITHUB_REPOSITORY}" \ + -f default_branch="${{ steps.created.outputs.branch }}" diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..745da95 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,111 @@ +name: Pages + +# One version of the site per branch, published by mike into gh-pages: 5.2 is served at /5.2/, and the +# alias "latest" follows the highest version, which is what the root of the site redirects to. A branch is +# named 5.2, or 5.2.x like the maintenance branches of a library; 5.x is the next minor. Pages is +# configured to deploy from the gh-pages branch, root folder. +on: + push: + branches: + - '[0-9]*.[0-9]*' + - '[0-9]*.x' + paths-ignore: + - '.github/workflows/check.yml' + workflow_dispatch: + +# MkDocs 2.0 is announced as backward incompatible with every plugin and theme, so the toolchain is pinned +# to the 1.x line in requirements.txt and the two notices about it are silenced: they are news about the +# ecosystem, not findings about this site. +env: + NO_MKDOCS_2_WARNING: 'true' + DISABLE_MKDOCS_2_WARNING: 'true' + +permissions: + contents: write + +# Two branches pushed at once must not write gh-pages at the same time, and a queued run is never +# cancelled: it carries a version the other does not. +concurrency: + group: gh-pages + cancel-in-progress: false + +jobs: + deploy: + name: Deploy ${{ github.ref_name }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + cache: pip + cache-dependency-path: requirements.txt + + - name: Install the toolchain + run: pip install -r requirements.txt + + - name: Identify the committer + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # The version a branch publishes: 5.2.x publishes 5.2, any other name publishes itself. + - name: The version of this branch + id: version + run: | + version="${GITHUB_REF_NAME}" + if [[ "$version" =~ ^[0-9]+\.[0-9]+\.x$ ]]; then + version="${version%.x}" + fi + echo "The branch '${GITHUB_REF_NAME}' publishes the version '$version'." + echo "name=$version" >> "$GITHUB_OUTPUT" + + # The highest version branch of the repository, by version order, in which a "1.x" branch comes + # after "1.2" and "1.2.x": it is where the next minor is written. sort -V alone puts "1.x" first, + # so each name is sorted by a key where "x" is a large number. Only that branch carries "latest". + - name: Is this the newest version? + id: newest + run: | + git fetch --quiet origin '+refs/heads/*:refs/remotes/origin/*' + newest=$(git for-each-ref --format='%(refname:lstrip=3)' 'refs/remotes/origin/*' \ + | grep -E '^[0-9]+\.([0-9]+(\.x)?|x)$' \ + | awk '{ key = $0; if (key ~ /^[0-9]+\.x$/) sub(/x$/, "999999", key); else sub(/\.x$/, "", key); print key " " $0 }' \ + | sort -V -k1,1 \ + | tail -n 1 \ + | cut -d' ' -f2) + echo "The newest version branch is '$newest', this one is '${GITHUB_REF_NAME}'." + if [ "$newest" = "${GITHUB_REF_NAME}" ]; then + echo "latest=true" >> "$GITHUB_OUTPUT" + else + echo "latest=false" >> "$GITHUB_OUTPUT" + fi + + - name: Publish this version + if: steps.newest.outputs.latest == 'false' + run: mike deploy --push --update-aliases "${{ steps.version.outputs.name }}" + + - name: Publish this version as the latest one + if: steps.newest.outputs.latest == 'true' + run: | + mike deploy --push --update-aliases "${{ steps.version.outputs.name }}" latest + mike set-default --push latest + + # The root of gh-pages is not a version: mike leaves it alone, and it holds what GitHub Pages reads + # there. site-root/ in this branch is copied over it: CNAME, the domain of the site, and 404.html, + # which sends the addresses of the former GitBook site to the latest version. + - name: Publish the root of the site + run: | + git fetch --quiet origin gh-pages + git worktree add "${RUNNER_TEMP}/gh-pages" origin/gh-pages + cp -R site-root/. "${RUNNER_TEMP}/gh-pages/" + cd "${RUNNER_TEMP}/gh-pages" + git add --all + if git diff --cached --quiet; then + echo "The root of the site is up to date." + else + git commit --quiet -m "Publish the root of the site from ${GITHUB_REF_NAME}" + git push origin HEAD:gh-pages + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f576159 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/site/ +/.venv/ +/.cache/ +__pycache__/ +.idea/ +.claude/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..80eb70b --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +# The documentation of Beffroi. "make install" once, "make serve" to write, "make check" before a pull +# request. Every target runs in .venv, created from requirements.txt, with uv when it is installed. + +VENV := .venv +BIN := $(VENV)/bin +UV := $(shell command -v uv 2>/dev/null) + +# News about the MkDocs ecosystem, not findings about this site. requirements.txt pins the 1.x line. +export NO_MKDOCS_2_WARNING := true +export DISABLE_MKDOCS_2_WARNING := true + +.DEFAULT_GOAL := help +.PHONY: help install serve build check versions clean + +help: ## Show this help + @grep -E '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}' + +$(BIN)/mkdocs: requirements.txt +ifdef UV + uv venv --allow-existing $(VENV) + VIRTUAL_ENV=$(VENV) uv pip install -r requirements.txt +else + python3 -m venv $(VENV) + $(BIN)/pip install --upgrade pip + $(BIN)/pip install -r requirements.txt +endif + @touch $(BIN)/mkdocs + +install: $(BIN)/mkdocs ## Install the toolchain in .venv + +serve: install ## Serve the site on http://127.0.0.1:8000, rebuilt as you save + $(BIN)/mkdocs serve + +build: install ## Build the site into site/ + $(BIN)/mkdocs build + +check: install ## Build strictly: a broken link, a missing anchor or a page outside the navigation fails + $(BIN)/mkdocs build --strict + +versions: install ## List the versions published on gh-pages + $(BIN)/mike list + +clean: ## Remove the build and the virtual environment + rm -rf site $(VENV) diff --git a/README.md b/README.md index 092db54..65a6fcf 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,52 @@ ---- -description: >- - Seamless Integration with PHP Applications: Our WebAuthn Documentation and - Tutorials -layout: landing ---- +# The documentation of WebAuthn Framework -# WebAuthn: Strong Authentication for your PHP applications +The public documentation of [WebAuthn Framework](https://github.com/web-auth/webauthn-framework), WebAuthn +and passkeys for PHP and Symfony: the ceremonies, the pure PHP library, the Symfony bundle and the Symfony +UX components. -Welcome to WebAuthn, the solution for strong authentication on the web. With WebAuthn, you can offer your users a secure, simple, and convenient authentication experience that seamlessly integrates with your PHP or Symfony application. +Published with [MkDocs](https://www.mkdocs.org/) and +[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) on GitHub Pages: +**** -At Spomky-Labs, we are committed to making the web safer for all users. We provide free, libre, and open-source libraries and a Symfony bundle to help you implement strong authentication solutions for your websites and applications. +## Writing -[WebAuthn](https://www.w3.org/TR/webauthn-2/) is an open-source technology developed by the [World Wide Web Consortium (W3C)](https://www.w3.org/) and is compatible with all modern web browsers. With WebAuthn and PHP, you can offer your users secure authentication without requiring them to remember complex passwords or provide sensitive personal information. +```bash +make install # the toolchain, in .venv, from requirements.txt +make serve # http://127.0.0.1:8000, rebuilt as you save +make check # the strict build continuous integration runs +``` -WebAuthn is supported by the FIDO Alliance, an organization working to promote open and interoperable strong authentication standards. You can learn more about the FIDO Alliance by visiting their website at [https://fidoalliance.org/](https://fidoalliance.org/). +`make check` is the gate: a broken internal link, an anchor that does not exist, a page absent from the +navigation or a navigation entry without a page fails the build. -Explore our documentation to learn more about WebAuthn and how it can help you implement strong authentication in your PHP or Symfony application. +## Where things are -Do you prefer reading code? Every ceremony this documentation describes is shown at work in [web-auth/webauthn-demos](https://github.com/web-auth/webauthn-demos): seven pure PHP demos and a full Symfony application you can run locally. See [the dedicated page](the-project/demos.md). +| Path | What it is | +|---|---| +| `mkdocs.yml` | The site: theme, extensions, plugins, and the whole navigation | +| `docs/` | The pages, one directory per section of the navigation | +| `docs/assets/stylesheets/` | The look: `slabs-tokens.css`, copied as it is from the Slabs design kit, and `slabs.css`, which applies the guidelines to the theme | +| `docs/assets/brand/`, `docs/assets/fonts/` | The project mark, the signature, the favicons and the share image of the design kit, and its typefaces (SIL Open Font License) | +| `docs/overrides/` | Theme partials: the logo, the footer, the link to the repository | +| `site-root/` | The root of the published site, copied to the root of the `gh-pages` branch: `CNAME`, and the `404.html` that sends the addresses of the former GitBook site to their version | +| `requirements.txt` | The toolchain, pinned. Continuous integration installs exactly this | +| `.github/workflows/check.yml` | The strict build, on every pull request | +| `.github/workflows/pages.yml` | The publication: one version per branch, through mike | +| `.github/workflows/default-branch.yml` | A new version branch becomes the default branch | +| `docs/SUMMARY.md`, `.gitbook.yaml` | The GitBook space, until the domain points to GitHub Pages | + +How the documentation left GitBook, and the rules every documentation site of Slabs follows, are in +[Spomky-Labs/docs](https://github.com/Spomky-Labs/docs/blob/main/MIGRATING.md). + +## One branch per version + +A branch of this repository is a version of the packages: `5.2`, `5.3`, `5.4`, the three minor versions +the library maintains. A push to one of them publishes it under its own name, and the newest one also +carries the `latest` alias, which the root of the site redirects to. A fix that applies to several +versions is made on each of them. + +```bash +git switch -c 5.5 5.4 # from the version it continues +# in mkdocs.yml, point edit_uri at the new branch +git push -u origin 5.5 # publishes it, and moves "latest" and the default branch +``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..80bab4e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +--- +description: >- + Seamless Integration with PHP Applications: Our WebAuthn Documentation and + Tutorials +--- + +# WebAuthn: Strong authentication for your PHP applications + +Welcome to WebAuthn, the solution for strong authentication on the web. With WebAuthn, you can offer your users a secure, simple, and convenient authentication experience that seamlessly integrates with your PHP or Symfony application. + +At Spomky-Labs, we are committed to making the web safer for all users. We provide free, libre, and open-source libraries and a Symfony bundle to help you implement strong authentication solutions for your websites and applications. + +[WebAuthn](https://www.w3.org/TR/webauthn-2/) is an open-source technology developed by the [World Wide Web Consortium (W3C)](https://www.w3.org/) and is compatible with all modern web browsers. With WebAuthn and PHP, you can offer your users secure authentication without requiring them to remember complex passwords or provide sensitive personal information. + +WebAuthn is supported by the FIDO Alliance, an organization working to promote open and interoperable strong authentication standards. You can learn more about the FIDO Alliance by visiting their website at [https://fidoalliance.org/](https://fidoalliance.org/). + +Explore our documentation to learn more about WebAuthn and how it can help you implement strong authentication in your PHP or Symfony application. + +Do you prefer reading code? Every ceremony this documentation describes is shown at work in [web-auth/webauthn-demos](https://github.com/web-auth/webauthn-demos): seven pure PHP demos and a full Symfony application you can run locally. See [the dedicated page](the-project/demos.md). diff --git a/SUMMARY.md b/docs/SUMMARY.md similarity index 100% rename from SUMMARY.md rename to docs/SUMMARY.md diff --git a/docs/assets/brand/apple-touch-icon.png b/docs/assets/brand/apple-touch-icon.png new file mode 100644 index 0000000..e384e06 Binary files /dev/null and b/docs/assets/brand/apple-touch-icon.png differ diff --git a/docs/assets/brand/favicon.ico b/docs/assets/brand/favicon.ico new file mode 100644 index 0000000..901a26c Binary files /dev/null and b/docs/assets/brand/favicon.ico differ diff --git a/docs/assets/brand/favicon.svg b/docs/assets/brand/favicon.svg new file mode 100644 index 0000000..4b601fb --- /dev/null +++ b/docs/assets/brand/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/brand/marque-webauthn-framework-couleur.svg b/docs/assets/brand/marque-webauthn-framework-couleur.svg new file mode 100644 index 0000000..d6ff459 --- /dev/null +++ b/docs/assets/brand/marque-webauthn-framework-couleur.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/brand/marque-webauthn-framework-inverse.svg b/docs/assets/brand/marque-webauthn-framework-inverse.svg new file mode 100644 index 0000000..a949130 --- /dev/null +++ b/docs/assets/brand/marque-webauthn-framework-inverse.svg @@ -0,0 +1 @@ + diff --git a/docs/assets/brand/og-slabs-1200x630.png b/docs/assets/brand/og-slabs-1200x630.png new file mode 100644 index 0000000..4c3d9fd Binary files /dev/null and b/docs/assets/brand/og-slabs-1200x630.png differ diff --git a/docs/assets/brand/slabs-logotype-signature-inverse.svg b/docs/assets/brand/slabs-logotype-signature-inverse.svg new file mode 100644 index 0000000..3fa1506 --- /dev/null +++ b/docs/assets/brand/slabs-logotype-signature-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/assets/fonts/IBMPlexMono-Italic.woff2 b/docs/assets/fonts/IBMPlexMono-Italic.woff2 new file mode 100644 index 0000000..3948c82 Binary files /dev/null and b/docs/assets/fonts/IBMPlexMono-Italic.woff2 differ diff --git a/docs/assets/fonts/IBMPlexMono-Regular.woff2 b/docs/assets/fonts/IBMPlexMono-Regular.woff2 new file mode 100644 index 0000000..01a68eb Binary files /dev/null and b/docs/assets/fonts/IBMPlexMono-Regular.woff2 differ diff --git a/docs/assets/fonts/IBMPlexMono-SemiBold.woff2 b/docs/assets/fonts/IBMPlexMono-SemiBold.woff2 new file mode 100644 index 0000000..7134ce0 Binary files /dev/null and b/docs/assets/fonts/IBMPlexMono-SemiBold.woff2 differ diff --git a/docs/assets/fonts/OFL-IBMPlexMono.txt b/docs/assets/fonts/OFL-IBMPlexMono.txt new file mode 100644 index 0000000..c35c4c6 --- /dev/null +++ b/docs/assets/fonts/OFL-IBMPlexMono.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/OFL-SpaceGrotesk.txt b/docs/assets/fonts/OFL-SpaceGrotesk.txt new file mode 100644 index 0000000..cb512b9 --- /dev/null +++ b/docs/assets/fonts/OFL-SpaceGrotesk.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/SpaceGrotesk-Variable.woff2 b/docs/assets/fonts/SpaceGrotesk-Variable.woff2 new file mode 100644 index 0000000..477234a Binary files /dev/null and b/docs/assets/fonts/SpaceGrotesk-Variable.woff2 differ diff --git a/.gitbook/assets/FIDO_Passkey_mark_A_black-e1702581853266.png b/docs/assets/images/fido-passkey-mark-a-black-e1702581853266.png similarity index 100% rename from .gitbook/assets/FIDO_Passkey_mark_A_black-e1702581853266.png rename to docs/assets/images/fido-passkey-mark-a-black-e1702581853266.png diff --git a/.gitbook/assets/fido2.jpeg b/docs/assets/images/fido2.jpeg similarity index 100% rename from .gitbook/assets/fido2.jpeg rename to docs/assets/images/fido2.jpeg diff --git a/.gitbook/assets/images (1).jpeg b/docs/assets/images/images-1.jpeg similarity index 100% rename from .gitbook/assets/images (1).jpeg rename to docs/assets/images/images-1.jpeg diff --git a/.gitbook/assets/login.png b/docs/assets/images/login.png similarity index 100% rename from .gitbook/assets/login.png rename to docs/assets/images/login.png diff --git a/.gitbook/assets/master.jpg b/docs/assets/images/master.jpg similarity index 100% rename from .gitbook/assets/master.jpg rename to docs/assets/images/master.jpg diff --git a/.gitbook/assets/registration (2).png b/docs/assets/images/registration-2.png similarity index 100% rename from .gitbook/assets/registration (2).png rename to docs/assets/images/registration-2.png diff --git a/.gitbook/assets/webauthn-android-fennec (1).png b/docs/assets/images/webauthn-android-fennec-1.png similarity index 100% rename from .gitbook/assets/webauthn-android-fennec (1).png rename to docs/assets/images/webauthn-android-fennec-1.png diff --git a/docs/assets/stylesheets/slabs-tokens.css b/docs/assets/stylesheets/slabs-tokens.css new file mode 100644 index 0000000..2bbe80e --- /dev/null +++ b/docs/assets/stylesheets/slabs-tokens.css @@ -0,0 +1,86 @@ +/* + * Slabs design tokens, brand guidelines v2.0 (October 2026). + * Source: Charte Graphique.pdf; page numbers in comments. + * Use these values as they are: no new color, no tint of orange, no other radius. + */ +:root { + /* Brand colors (p. 17) */ + --slabs-anthracite: #333334; /* logo, text, dark grounds */ + --slabs-beton: #B3AEA6; /* secondary grounds, rules, text on dark */ + --slabs-calcaire: #F5F1E8; /* light grounds, logo on dark */ + --slabs-orange: #ED6A20; /* the cursor, the slab, the main action; never text on light, never a text background */ + + /* Complementary colors (p. 19): small surfaces, always with a word, text on light grounds only */ + --slabs-acier: #4A5D6E; /* information, notes, neutral data series */ + --slabs-patine: #3E7268; /* success, validation, second data series */ + --slabs-brique: #A6452C; /* errors, alerts, negative values; never a button */ + + /* Utility colors (p. 20): screen only, never brand colors */ + --slabs-schiste: #5E5B56; /* secondary text, link underline, field borders */ + --slabs-calcaire-clair: #FBF9F4; /* cards, panels, menus, state notes */ + --slabs-calcaire-ombre: #ECE7DD; /* notes, tags, inline code, disabled controls */ + --slabs-platre: #DCD5C6; /* rules on light grounds; types and properties in code */ + --slabs-sable: #E8C9A8; /* strings and numbers in code on Anthracite only */ + + /* Chart series, in order (p. 21): orange for the series the chart is about, Brique for negative values only */ + --slabs-series-1: var(--slabs-orange); + --slabs-series-2: var(--slabs-anthracite); + --slabs-series-3: var(--slabs-acier); + --slabs-series-4: var(--slabs-patine); + --slabs-series-5: var(--slabs-schiste); + --slabs-series-negative: var(--slabs-brique); + + /* Code on Anthracite (p. 34) */ + --slabs-code-bg: var(--slabs-anthracite); + --slabs-code-text: var(--slabs-calcaire); /* text, variables; keywords in SemiBold */ + --slabs-code-type: var(--slabs-platre); /* types, properties */ + --slabs-code-string: var(--slabs-sable); /* strings, numbers */ + --slabs-code-comment: var(--slabs-beton); /* comments, italic */ + + /* Typefaces (p. 22) */ + --slabs-font-sans: "Space Grotesk", Arial, sans-serif; + --slabs-font-mono: "IBM Plex Mono", Consolas, monospace; /* 400 and 600 only on the web */ + + /* Web type scale (p. 34): desktop, then mobile */ + --slabs-h1: 42px; --slabs-h1-mobile: 32px; --slabs-h1-lh: 1.1; /* Bold 700 */ + --slabs-h2: 32px; --slabs-h2-mobile: 26px; --slabs-h2-lh: 1.2; /* Bold 700 */ + --slabs-h3: 26px; --slabs-h3-mobile: 22px; --slabs-h3-lh: 1.3; /* SemiBold 600 */ + --slabs-h4: 22px; --slabs-h4-mobile: 18px; --slabs-h4-lh: 1.4; /* SemiBold 600 */ + --slabs-h5: 15px; --slabs-h5-mobile: 14px; --slabs-h5-tracking: 0.08em; /* Plex Mono 600, capitals */ + --slabs-h6: 13px; --slabs-h6-tracking: 0.12em; /* Plex Mono 600, capitals */ + --slabs-body-reading: 18px; --slabs-body-reading-mobile: 16px; + --slabs-body: 16px; + --slabs-code-size: 14px; + --slabs-caption: 12px; + --slabs-label-tracking: 0.12em; /* Plex Mono 600 capitals */ + --slabs-nav: 15px; /* Space Grotesk 500 */ + + /* Line heights (p. 34) */ + --slabs-lh-ui: 1.5; + --slabs-lh-reading: 1.6; + --slabs-lh-code: 1.55; + + /* Layout (p. 31) */ + --slabs-content-max: 1200px; /* measured in the frame: the window minus the side band */ + --slabs-reading-max: 864px; /* about 80 characters, at least 72 % of the content area */ + --slabs-side-band: 288px; + --slabs-gutter: 24px; + --slabs-gutter-mobile: 16px; + --slabs-space-base: 4px; /* spacing in multiples of 4 px, from 4 to 96 px */ + --slabs-space-max: 96px; + --slabs-radius-card: 6px; + --slabs-radius-control: 4px; + --slabs-header-logo: 32px; /* logotype height in the header */ + /* Breakpoints, where the layout stops fitting: 640px and 1024px with a header, about 1100px with a side band */ + + /* Lines and focus (p. 33) */ + --slabs-link-underline: 1px; /* Schiste; 2px orange on hover and active */ + --slabs-focus-width: 2px; /* Anthracite on light grounds, orange on dark */ + --slabs-focus-offset: 2px; + --slabs-field-border: 1px; /* Schiste; 2px Brique in error */ + + /* Icons (Icones library) */ + --slabs-icon-size: 24px; + --slabs-icon-stroke: 2px; + /* --slabs-icon-accent: #ED6A20; set on an icon or a parent to turn the accent on */ +} diff --git a/docs/assets/stylesheets/slabs.css b/docs/assets/stylesheets/slabs.css new file mode 100644 index 0000000..0255cef --- /dev/null +++ b/docs/assets/stylesheets/slabs.css @@ -0,0 +1,1120 @@ +/* + * The Slabs brand guidelines v2.0, applied to Material for MkDocs. + * + * Every value comes from slabs-tokens.css, loaded before this file and copied from the design kit as it is. + * This file only says which token goes where. The pages of the guidelines it follows are cited by number: + * p. 30 for documentation, p. 31 for the frame, p. 33 for components, p. 34 for reading and code. + * + * Two schemes. The light one is the guidelines: Calcaire ground, Anthracite text. The dark one keeps the + * same rules on an Anthracite ground, as the guidelines do for every dark surface: Calcaire text, Béton + * for what is secondary, notes on Calcaire clair (p. 19), focus in orange (p. 33). + * + * Signal orange marks the cursor of the logo, the slab of a note, the cursor of a primary button and the + * hovered link. Never text, never a background, never a tint. + */ + +/* ---------------------------------------------------------------- Typefaces (p. 22) */ + +@font-face { + font-display: swap; + font-family: "Space Grotesk"; + font-style: normal; + font-weight: 300 700; + src: url("../fonts/SpaceGrotesk-Variable.woff2") format("woff2"); +} + +/* IBM Plex Mono is served in 400 and 600 only on the web; the italic is for comments in code. */ +@font-face { + font-display: swap; + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 400; + src: url("../fonts/IBMPlexMono-Regular.woff2") format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "IBM Plex Mono"; + font-style: italic; + font-weight: 400; + src: url("../fonts/IBMPlexMono-Italic.woff2") format("woff2"); +} + +@font-face { + font-display: swap; + font-family: "IBM Plex Mono"; + font-style: normal; + font-weight: 600; + src: url("../fonts/IBMPlexMono-SemiBold.woff2") format("woff2"); +} + +/* ---------------------------------------------------------------- The two schemes */ + +[data-md-color-scheme="default"] { + --doc-ground: var(--slabs-calcaire); + --doc-raised: var(--slabs-calcaire-clair); + --doc-tint: var(--slabs-calcaire-ombre); + --doc-text: var(--slabs-anthracite); + --doc-text-secondary: var(--slabs-schiste); + --doc-rule: var(--slabs-platre); + --doc-rule-strong: var(--slabs-anthracite); + --doc-link-underline: var(--slabs-schiste); + --doc-focus: var(--slabs-anthracite); + --doc-code-edge: var(--slabs-anthracite); + --doc-field: #ffffff; + --doc-field-border: var(--slabs-schiste); + --doc-note-ground: var(--slabs-calcaire-ombre); +} + +[data-md-color-scheme="slate"] { + --doc-ground: var(--slabs-anthracite); + --doc-raised: var(--slabs-anthracite); + --doc-tint: var(--slabs-schiste); + --doc-text: var(--slabs-calcaire); + --doc-text-secondary: var(--slabs-beton); + --doc-rule: var(--slabs-schiste); + --doc-rule-strong: var(--slabs-calcaire); + --doc-link-underline: var(--slabs-beton); + --doc-focus: var(--slabs-orange); + /* A code block is Anthracite on an Anthracite ground: a Schiste line keeps its edge. */ + --doc-code-edge: var(--slabs-schiste); + --doc-field: var(--slabs-anthracite); + --doc-field-border: var(--slabs-beton); + --doc-note-ground: var(--slabs-calcaire-clair); +} + +[data-md-color-scheme="default"], +[data-md-color-scheme="slate"] { + --md-text-font: "Space Grotesk"; + --md-code-font: "IBM Plex Mono"; + + --md-default-fg-color: var(--doc-text); + --md-default-fg-color--light: var(--doc-text-secondary); + --md-default-fg-color--lighter: var(--doc-text-secondary); + --md-default-fg-color--lightest: var(--doc-rule); + --md-default-bg-color: var(--doc-ground); + --md-default-bg-color--light: var(--doc-ground); + --md-default-bg-color--lighter: var(--doc-ground); + --md-default-bg-color--lightest: var(--doc-ground); + + --md-primary-fg-color: var(--doc-text); + --md-primary-fg-color--light: var(--doc-text); + --md-primary-fg-color--dark: var(--doc-text); + --md-primary-bg-color: var(--doc-ground); + --md-primary-bg-color--light: var(--doc-text-secondary); + --md-accent-fg-color: var(--doc-text); + --md-accent-fg-color--transparent: var(--doc-tint); + --md-accent-bg-color: var(--doc-ground); + + --md-typeset-color: var(--doc-text); + --md-typeset-a-color: var(--doc-text); + --md-typeset-mark-color: var(--doc-tint); + --md-typeset-table-color: var(--doc-rule); + + --md-code-fg-color: var(--slabs-code-text); + --md-code-bg-color: var(--slabs-code-bg); + --md-code-hl-color: var(--slabs-schiste); + --md-code-hl-number-color: var(--slabs-code-string); + --md-code-hl-string-color: var(--slabs-code-string); + --md-code-hl-special-color: var(--slabs-code-string); + --md-code-hl-constant-color: var(--slabs-code-type); + --md-code-hl-name-color: var(--slabs-code-text); + --md-code-hl-function-color: var(--slabs-code-text); + --md-code-hl-keyword-color: var(--slabs-code-text); + --md-code-hl-operator-color: var(--slabs-code-text); + --md-code-hl-punctuation-color: var(--slabs-code-text); + --md-code-hl-variable-color: var(--slabs-code-text); + --md-code-hl-generic-color: var(--slabs-code-text); + --md-code-hl-comment-color: var(--slabs-code-comment); + --md-code-hl-color--light: var(--slabs-schiste); + + --md-footer-fg-color: var(--slabs-calcaire); + --md-footer-fg-color--light: var(--slabs-beton); + --md-footer-fg-color--lighter: var(--slabs-beton); + --md-footer-bg-color: var(--slabs-anthracite); + --md-footer-bg-color--dark: var(--slabs-anthracite); + + --md-shadow-z1: none; + --md-shadow-z2: none; + --md-shadow-z3: none; +} + +body { + -webkit-font-smoothing: antialiased; + background-color: var(--doc-ground); + text-rendering: optimizeLegibility; +} + +/* Focus, always visible (p. 33). */ +:focus-visible, +.md-typeset :focus-visible { + outline: var(--slabs-focus-width) solid var(--doc-focus); + outline-offset: var(--slabs-focus-offset); +} + +/* ---------------------------------------------------------------- Header (p. 31) */ + +.md-header, +.md-header--shadow { + background-color: var(--doc-raised); + border-bottom: 1px solid var(--doc-rule); + box-shadow: none; + color: var(--doc-text); +} + +/* The logo, 32 px high: the project mark, or the Slabs logotype for a project that has none yet. The + colour version on light grounds, the reversed one on dark. */ +.md-header__button.md-logo { + margin: 0 0 0 var(--slabs-gutter-mobile); + padding: 0; +} + +.md-logo .sl-logo, +.md-header__button.md-logo img { + display: block; + height: var(--slabs-header-logo); + width: auto; +} + +/* In the drawer of a narrow screen, the mark takes the width of the drawer. */ +.md-nav__button.md-logo .sl-logo { + height: auto; + max-width: 100%; +} + +[data-md-color-scheme="default"] .sl-logo--dark, +[data-md-color-scheme="slate"] .sl-logo--light { + display: none !important; +} + +/* The title of the page, after a rule, once the reader has scrolled past its heading. A project mark + already names the project: on a screen wide enough to show it, the name is not written a second time. + Beside the Slabs logotype, the name stays. */ +.md-header__title { + font-size: var(--slabs-nav); + font-weight: 600; + margin-left: var(--slabs-gutter); +} + +.md-header__title .md-header__ellipsis { + border-left: 1px solid var(--doc-rule); + padding-left: var(--slabs-gutter); +} + +@media screen and (min-width: 76.25em) { + .md-header:has(.sl-logo--mark) .md-header__title:not(.md-header__title--active) .md-header__ellipsis { + border-left-color: transparent; + } + + .md-header:has(.sl-logo--mark) .md-header__title .md-header__topic:first-child { + visibility: hidden; + } +} + +.md-header__button, +.md-header__topic, +.md-header__option, +.md-header__source { + color: var(--doc-text); +} + +.md-header__source { + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); +} + +.md-source__repository, +.md-source__facts { + font-family: var(--md-code-font-family); +} + +/* The search field is a field (p. 33): the ground of a field, a 1 px border, the radius of a control. */ +.md-search__form { + background-color: var(--doc-field); + border: var(--slabs-field-border) solid var(--doc-field-border); + border-radius: var(--slabs-radius-control); + box-shadow: none; +} + +.md-search__form:hover { + background-color: var(--doc-field); +} + +[data-md-toggle="search"]:checked ~ .md-header .md-search__form { + background-color: var(--doc-field); + border-radius: var(--slabs-radius-control) var(--slabs-radius-control) 0 0; +} + +.md-search__input, +.md-search__icon, +.md-search__options .md-icon { + color: var(--doc-text); +} + +.md-search__input { + font-size: var(--slabs-body); +} + +.md-search__input::placeholder { + color: var(--doc-text-secondary); +} + +.md-search__output { + border: var(--slabs-field-border) solid var(--doc-field-border); + border-radius: 0 0 var(--slabs-radius-control) var(--slabs-radius-control); + border-top: 0; +} + +.md-search__scrollwrap, +.md-search-result { + background-color: var(--doc-raised); +} + +.md-search-result__meta { + background-color: var(--doc-tint); + color: var(--doc-text-secondary); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); +} + +.md-search-result__link:is(:focus, :hover) { + background-color: var(--doc-tint); +} + +.md-search-result mark { + color: var(--doc-text); + text-decoration: underline var(--slabs-orange) 2px; + text-underline-offset: 0.2em; +} + +/* The version is a control: Plex Mono, a 1 px rule, the radius of a control. */ +.md-version { + font-family: var(--md-code-font-family); +} + +.md-version__current { + border: 1px solid var(--doc-rule); + border-radius: var(--slabs-radius-control); + color: var(--doc-text-secondary); + font-size: var(--slabs-caption); + margin-left: var(--slabs-gutter-mobile); + padding: 0 calc(var(--slabs-space-base) * 2); +} + +.md-version__current::after { + background-color: var(--doc-text-secondary); +} + +.md-version__list { + background-color: var(--doc-raised); + border: 1px solid var(--doc-rule); + border-radius: var(--slabs-radius-card); + box-shadow: none; +} + +.md-version__link { + font-size: var(--slabs-caption); +} + +.md-version__link:is(:focus, :hover) { + background-color: var(--doc-tint); + color: var(--doc-text); +} + +/* ---------------------------------------------------------------- Layout (p. 31) */ + +/* The navigation and the contents of the page take the width of the side band, 288 px each: the 168 px + the guidelines leave beside the reading column broke a title in two on most pages. The reading column + keeps its 864 px. */ +.md-grid { + max-width: calc(2 * var(--slabs-side-band) + var(--slabs-reading-max) + 2 * var(--slabs-gutter)); +} + +@media screen and (min-width: 76.25em) { + .md-sidebar--primary { + width: var(--slabs-side-band); + } + + .md-sidebar--secondary { + width: var(--slabs-side-band); + } +} + +@media screen and (min-width: 60em) { + .md-content__inner { + margin-left: auto; + margin-right: auto; + max-width: var(--slabs-reading-max); + } +} + +.md-main { + background-color: var(--doc-ground); +} + +/* ---------------------------------------------------------------- Navigation (p. 31) */ + +.md-nav { + font-size: var(--slabs-nav); + line-height: var(--slabs-lh-ui); +} + +.md-nav__link { + color: var(--doc-text); + font-weight: 500; + margin-top: calc(var(--slabs-space-base) * 2); +} + +.md-nav__link:is(:focus, :hover), +.md-nav__link[for]:is(:focus, :hover) { + color: var(--doc-text); +} + +/* The current page: SemiBold and a 2 px underline in the colour of the text. */ +.md-nav__link--active, +.md-nav__item .md-nav__link--active:hover { + color: var(--doc-text); + font-weight: 600; +} + +/* The underline is drawn on the text of the link only: drawn on the link as well, it doubles. */ +.md-nav--primary .md-nav__link { + text-decoration: none; +} + +.md-nav--primary .md-nav__link--active > .md-ellipsis { + text-decoration: underline 2px var(--doc-rule-strong); + text-underline-offset: 0.35em; +} + +/* Under the pointer, a link of the navigation behaves as a link of the text (p. 33): a 2 px orange + underline, on the current page as on the others. */ +.md-nav--primary a.md-nav__link:is(:hover, :focus-visible) > .md-ellipsis { + text-decoration: underline 2px var(--slabs-orange); + text-underline-offset: 0.35em; +} + +/* A section title is a label: Plex Mono SemiBold capitals, 12 px, tracked, in the secondary colour. */ +.md-nav--primary .md-nav__item--section > .md-nav__link, +.md-nav--primary .md-nav__item--section > .md-nav__container > .md-nav__link { + color: var(--doc-text-secondary); + cursor: default; + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + font-weight: 600; + letter-spacing: var(--slabs-label-tracking); + margin-top: calc(var(--slabs-space-base) * 6); + text-decoration: none; + text-transform: uppercase; +} + +.md-nav--primary .md-nav__item--section > .md-nav__link .md-nav__icon { + display: none; +} + +@media screen and (min-width: 76.25em) { + .md-nav--primary > .md-nav__title { + display: none; + } +} + +/* The drawer of a narrow screen opens on the same band as the header. */ +.md-nav--primary > .md-nav__title, +.md-nav--primary > .md-nav__title .md-nav__button { + background-color: var(--doc-raised); + color: var(--doc-text); +} + +.md-nav--primary .md-nav__title { + box-shadow: none; + font-weight: 600; +} + +.md-sidebar { + background-color: var(--doc-ground); +} + +@media screen and (max-width: 76.234375em) { + .md-nav--primary .md-nav__title, + .md-nav--primary .md-nav__item { + border-top: 0; + } + + .md-nav--primary .md-nav__link { + margin-top: 0; + padding: calc(var(--slabs-space-base) * 2) var(--slabs-gutter-mobile); + } +} + +/* The contents of the page, on the right: the same label, then the headings. */ +.md-nav--secondary .md-nav__title { + background: transparent; + box-shadow: none; + color: var(--doc-text-secondary); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + font-weight: 600; + letter-spacing: var(--slabs-label-tracking); + text-transform: uppercase; +} + +.md-nav--secondary .md-nav__link { + color: var(--doc-text-secondary); + font-weight: 400; +} + +.md-nav--secondary .md-nav__link--active, +.md-nav--secondary .md-nav__link:is(:hover, :focus-visible) { + color: var(--doc-text); +} + +.md-nav--secondary a.md-nav__link:is(:hover, :focus-visible) { + text-decoration: underline 2px var(--slabs-orange); + text-underline-offset: 0.35em; +} + +/* ---------------------------------------------------------------- Reading (p. 34) */ + +/* The body of a page reads as an article: 18 px, 16 px on a phone, line height 1.6. Interface text, the + navigation, the fields and the buttons, keeps its own sizes. */ +.md-typeset { + color: var(--doc-text); + font-size: var(--slabs-body-reading); + line-height: var(--slabs-lh-reading); +} + +@media screen and (max-width: 640px) { + .md-typeset { + font-size: var(--slabs-body-reading-mobile); + } +} + +.md-typeset :is(h1, h2, h3, h4) { + color: var(--doc-text); + font-family: var(--md-text-font-family); + letter-spacing: 0; +} + +.md-typeset h1 { + font-size: var(--slabs-h1); + font-weight: 700; + line-height: var(--slabs-h1-lh); + margin: 0 0 calc(var(--slabs-space-base) * 6); +} + +.md-typeset h2 { + font-size: var(--slabs-h2); + font-weight: 700; + line-height: var(--slabs-h2-lh); + margin: calc(var(--slabs-space-base) * 12) 0 calc(var(--slabs-space-base) * 4); +} + +.md-typeset h3 { + font-size: var(--slabs-h3); + font-weight: 600; + line-height: var(--slabs-h3-lh); + margin: calc(var(--slabs-space-base) * 10) 0 calc(var(--slabs-space-base) * 3); +} + +.md-typeset h4 { + font-size: var(--slabs-h4); + font-weight: 600; + line-height: var(--slabs-h4-lh); + margin: calc(var(--slabs-space-base) * 8) 0 calc(var(--slabs-space-base) * 2); +} + +/* H5 and H6 are labels: Plex Mono SemiBold capitals. */ +.md-typeset h5, +.md-typeset h6 { + color: var(--doc-text); + font-family: var(--md-code-font-family); + font-weight: 600; + text-transform: uppercase; +} + +.md-typeset h5 { + font-size: var(--slabs-h5); + letter-spacing: var(--slabs-h5-tracking); +} + +.md-typeset h6 { + font-size: var(--slabs-h6); + letter-spacing: var(--slabs-h6-tracking); +} + +@media screen and (max-width: 640px) { + .md-typeset h1 { font-size: var(--slabs-h1-mobile); } + .md-typeset h2 { font-size: var(--slabs-h2-mobile); } + .md-typeset h3 { font-size: var(--slabs-h3-mobile); } + .md-typeset h4 { font-size: var(--slabs-h4-mobile); } + .md-typeset h5 { font-size: var(--slabs-h5-mobile); } +} + +.md-typeset .headerlink { + color: var(--doc-text-secondary); +} + +.md-typeset p, +.md-typeset li { + text-align: left; +} + +.md-typeset hr { + border-bottom: 1px solid var(--doc-rule); +} + +.md-typeset blockquote { + border-left: 0; + color: var(--doc-text-secondary); + padding-left: var(--slabs-gutter); +} + +/* The date of the last change and the pencil that edits a page: secondary, in Plex Mono. */ +.md-source-file, +.md-typeset .md-source-file__fact { + color: var(--doc-text-secondary); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); +} + +.md-content__button { + color: var(--doc-text-secondary); +} + +.md-content__button:hover { + color: var(--doc-text); +} + +/* ---------------------------------------------------------------- Links (p. 33) */ + +.md-typeset a { + color: var(--doc-text); + text-decoration: underline var(--slabs-link-underline) var(--doc-link-underline); + text-underline-offset: 0.2em; +} + +.md-typeset a:is(:hover, :active) { + color: var(--doc-text); + text-decoration-color: var(--slabs-orange); + text-decoration-thickness: 2px; +} + +.md-typeset a.headerlink, +.md-typeset a.md-button, +.md-typeset a.md-content__button { + text-decoration: none; +} + +/* ---------------------------------------------------------------- Code (p. 30, 34) */ + +.md-typeset code, +.md-typeset kbd, +.md-typeset pre { + font-feature-settings: "kern"; +} + +/* Inline code, on Calcaire ombré. */ +.md-typeset :not(pre) > code { + background-color: var(--doc-tint); + border-radius: var(--slabs-radius-control); + color: var(--doc-text); + font-size: var(--slabs-code-size); + padding: 0.1em 0.35em; + word-break: normal; +} + +/* In a heading, inline code follows the size of the heading. */ +.md-typeset :is(h1, h2, h3, h4) code { + font-size: 0.85em; +} + +/* A code block: Plex Mono 14 px on Anthracite, the radius of a card. */ +.md-typeset .highlight { + background-color: var(--slabs-code-bg); + border: 1px solid var(--doc-code-edge); + border-radius: var(--slabs-radius-card); + overflow: hidden; +} + +/* A numbered block is a table inside the block: the block already draws the edge. */ +.md-typeset .highlight .highlighttable, +.md-typeset .highlighttable { + background-color: var(--slabs-code-bg); + border: 0; + border-radius: 0; + margin: 0; +} + +.md-typeset .highlighttable :is(.linenos, .code) { + vertical-align: top; +} + +.md-typeset pre { + margin: 0; +} + +.md-typeset pre > code, +.md-typeset .highlighttable code { + background-color: var(--slabs-code-bg); + color: var(--slabs-code-text); + font-size: var(--slabs-code-size); + line-height: var(--slabs-lh-code); + padding: calc(var(--slabs-space-base) * 4) calc(var(--slabs-space-base) * 5); +} + +.md-typeset .highlight + .highlight, +.md-typeset .highlight + p, +.md-typeset p + .highlight { + margin-top: calc(var(--slabs-space-base) * 4); +} + +.md-typeset .highlight { + margin: calc(var(--slabs-space-base) * 4) 0; +} + +/* The filename of a block, a caption above the code. */ +.md-typeset .highlight span.filename { + background-color: var(--slabs-code-bg); + border-bottom: 1px solid var(--slabs-schiste); + color: var(--slabs-beton); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + font-weight: 400; + margin: 0; + padding: calc(var(--slabs-space-base) * 2) calc(var(--slabs-space-base) * 5); +} + +.md-typeset .highlight span.filename::before { + display: none; +} + +/* Line numbers, at the start of each line, in Béton behind a Schiste rule. */ +.md-typeset .highlight [data-linenos]::before { + background-color: var(--slabs-code-bg); + box-shadow: -1px 0 var(--slabs-schiste) inset; + color: var(--slabs-code-comment); + margin-right: calc(var(--slabs-space-base) * 4); + padding-right: calc(var(--slabs-space-base) * 3); +} + +/* The line numbers are anchors: they are not links of the text. */ +.md-typeset .highlighttable .linenos a, +.md-typeset .highlight .linenos a { + color: var(--slabs-code-comment); + text-decoration: none; +} + +.md-typeset .highlight .hll { + background-color: var(--md-code-hl-color); +} + +/* The copy button sits on the code, in Béton, with no box of its own. */ +.md-code__nav, +:hover > .md-code__nav { + background-color: transparent; +} + +.md-code__button, +.md-clipboard { + background-color: transparent; + color: var(--slabs-beton); +} + +:hover > * > .md-code__button { + color: var(--slabs-beton); +} + +.md-code__button:is(.focus-visible, :hover), +.md-clipboard:is(:focus, :hover) { + color: var(--slabs-calcaire); + outline-color: var(--slabs-orange); +} + +/* The five token styles: text and variables Calcaire, keywords Calcaire SemiBold, types and properties + Plâtre, strings and numbers Sable, comments Béton italic. Operators and punctuation are text. */ +.md-typeset .highlight :is(.n, .nv, .vc, .vg, .vi, .vm, .nf, .fm, .nl, .ni, .ne, .o, .p, .w, .x, .err) { + color: var(--slabs-code-text); + font-style: normal; + font-weight: 400; +} + +.md-typeset .highlight :is(.k, .kc, .kd, .kn, .kp, .kr, .ow) { + color: var(--slabs-code-text); + font-style: normal; + font-weight: 600; +} + +.md-typeset .highlight :is(.kt, .nc, .nn, .nx, .na, .nb, .bp, .nt, .nd, .no, .py) { + color: var(--slabs-code-type); + font-style: normal; + font-weight: 400; +} + +.md-typeset .highlight :is(.l, .ld, .s, .s1, .s2, .sa, .sb, .sc, .sd, .se, .sh, .si, .sr, .ss, .sx, .dl, + .m, .mb, .mf, .mh, .mi, .il, .mo) { + color: var(--slabs-code-string); + font-style: normal; + font-weight: 400; +} + +.md-typeset .highlight :is(.c, .c1, .ch, .cm, .cp, .cpf, .cs) { + color: var(--slabs-code-comment); + font-style: italic; + font-weight: 400; +} + +.md-typeset .highlight :is(.gd, .gi, .gh, .gu, .go, .gp) { + color: var(--slabs-code-text); +} + +/* The orange cursor marks the prompt of a console session (p. 30). */ +.md-typeset .highlight .gp { + color: var(--slabs-orange); +} + +/* Tabs, for alternatives the reader chooses between: labels in Plex Mono, the current one underlined. */ +.md-typeset .tabbed-labels > label { + color: var(--doc-text-secondary); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + font-weight: 600; + letter-spacing: var(--slabs-label-tracking); + text-transform: uppercase; +} + +/* The theme turns each label into an anchor, to link to a tab: it is not a link of the text. */ +.md-typeset .tabbed-labels a { + color: inherit; + text-decoration: none; +} + +.md-typeset .tabbed-labels > label:hover, +.md-typeset .tabbed-set > input:checked + label { + color: var(--doc-text); +} + +.md-typeset .tabbed-labels::before { + background-color: var(--doc-rule-strong); +} + +/* ---------------------------------------------------------------- Notes (p. 30) */ + +/* A tinted box with a slab on top, never a coloured side border. Every kind of note looks the same: the + word of its title says what it is, colour never does (p. 19). On a dark ground the box is Calcaire + clair and its text Anthracite, as a message on Anthracite always is (p. 19). */ +.md-typeset .admonition, +.md-typeset details { + --md-typeset-a-color: var(--slabs-anthracite); + + background-color: var(--doc-note-ground); + border: 0; + border-radius: var(--slabs-radius-card); + box-shadow: none; + color: var(--slabs-anthracite); + font-size: inherit; + margin: calc(var(--slabs-space-base) * 6) 0; + padding: calc(var(--slabs-space-base) * 5) calc(var(--slabs-space-base) * 5) calc(var(--slabs-space-base) * 4); + position: relative; +} + +/* The slab: a quarter of the size of an H4 high, 1.2 times as wide, flat, square ends, on the top edge. */ +.md-typeset .admonition::before, +.md-typeset details::before { + background-color: var(--slabs-orange); + content: ""; + height: calc(var(--slabs-h4) / 4); + left: calc(var(--slabs-space-base) * 5); + position: absolute; + top: 0; + width: calc(var(--slabs-h4) * 1.2); +} + +.md-typeset :is(.admonition, details) > :is(.admonition-title, summary) { + background-color: transparent !important; + border: 0; + color: var(--slabs-anthracite); + font-family: var(--md-code-font-family); + font-size: var(--slabs-h6); + font-weight: 600; + letter-spacing: var(--slabs-h6-tracking); + margin: 0 0 calc(var(--slabs-space-base) * 2); + padding: 0; + text-transform: uppercase; +} + +/* The icons of the theme are not the Slabs set: the word is enough. */ +.md-typeset :is(.admonition, details) > :is(.admonition-title, summary)::before { + display: none; +} + +.md-typeset details > summary { + cursor: pointer; + padding-right: calc(var(--slabs-space-base) * 6); +} + +.md-typeset details > summary::after { + background-color: var(--slabs-anthracite); + top: 0; +} + +.md-typeset .admonition > :last-child, +.md-typeset details > :last-child { + margin-bottom: 0; +} + +.md-typeset :is(.admonition, details) :is(a, a:hover) { + color: var(--slabs-anthracite); +} + +.md-typeset :is(.admonition, details) a { + text-decoration-color: var(--slabs-schiste); +} + +.md-typeset :is(.admonition, details) a:hover { + text-decoration-color: var(--slabs-orange); +} + +.md-typeset :is(.admonition, details) :not(pre) > code { + background-color: var(--slabs-calcaire-ombre); + color: var(--slabs-anthracite); +} + +[data-md-color-scheme="default"] .md-typeset :is(.admonition, details) :not(pre) > code { + background-color: var(--slabs-calcaire-clair); +} + +/* ---------------------------------------------------------------- Tables (p. 33) */ + +.md-typeset table:not([class]) { + background-color: transparent; + border: 0; + border-collapse: collapse; + border-radius: 0; + box-shadow: none; + display: table; + font-size: inherit; + width: 100%; +} + +/* The header: Plex Mono capitals over a rule in the colour of the text. */ +.md-typeset table:not([class]) th { + background-color: transparent; + border-bottom: 1px solid var(--doc-rule-strong); + color: var(--doc-text); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + font-weight: 600; + letter-spacing: var(--slabs-label-tracking); + text-transform: uppercase; + vertical-align: bottom; +} + +.md-typeset table:not([class]) :is(th, td) { + padding: calc(var(--slabs-space-base) * 3) calc(var(--slabs-space-base) * 4) calc(var(--slabs-space-base) * 3) 0; +} + +.md-typeset table:not([class]) :is(th, td):last-child { + padding-right: 0; +} + +/* Plâtre between the rows. */ +.md-typeset table:not([class]) td { + border-top: 0; + border-bottom: 1px solid var(--doc-rule); + vertical-align: top; +} + +.md-typeset table:not([class]) tbody tr:hover { + background-color: transparent; + box-shadow: none; +} + +.md-typeset__scrollwrap { + margin: calc(var(--slabs-space-base) * 6) 0; +} + +.md-typeset__table { + padding: 0; +} + +/* ---------------------------------------------------------------- Buttons (p. 33) */ + +.md-typeset .md-button { + border: 1px solid var(--doc-text); + border-radius: var(--slabs-radius-control); + color: var(--doc-text); + font-size: var(--slabs-body); + font-weight: 500; + padding: calc(var(--slabs-space-base) * 2) calc(var(--slabs-space-base) * 4); + position: relative; +} + +.md-typeset .md-button:is(:focus, :hover) { + background-color: transparent; + border-color: var(--doc-text); + color: var(--doc-text); +} + +/* The primary button: Anthracite, Calcaire text, the orange cursor after the label. On hover the cursor + stretches under the label. */ +.md-typeset .md-button--primary, +.md-typeset .md-button--primary:is(:focus, :hover) { + background-color: var(--slabs-anthracite); + border-color: var(--slabs-anthracite); + color: var(--slabs-calcaire); + padding-right: calc(var(--slabs-space-base) * 8); +} + +[data-md-color-scheme="slate"] .md-typeset .md-button--primary, +[data-md-color-scheme="slate"] .md-typeset .md-button--primary:is(:focus, :hover) { + background-color: var(--slabs-calcaire); + border-color: var(--slabs-calcaire); + color: var(--slabs-anthracite); +} + +.md-typeset .md-button--primary::after { + background-color: var(--slabs-orange); + bottom: calc(var(--slabs-space-base) * 2 + 0.2em); + content: ""; + height: 0.25em; + position: absolute; + right: calc(var(--slabs-space-base) * 4); + transition: left 150ms, right 150ms, bottom 150ms; + width: auto; + left: calc(100% - var(--slabs-space-base) * 4 - 0.6em); +} + +.md-typeset .md-button--primary:hover::after { + bottom: calc(var(--slabs-space-base) * 1); + left: calc(var(--slabs-space-base) * 4); + right: calc(var(--slabs-space-base) * 8); +} + +/* ---------------------------------------------------------------- Footer (p. 31) */ + +/* Anthracite, whatever the scheme: the previous and next pages, then the reversed signature, the links in + Calcaire and the legal line in Béton. */ +.md-footer { + background-color: var(--slabs-anthracite); + color: var(--slabs-calcaire); +} + +.md-footer__inner { + border-bottom: 1px solid var(--slabs-schiste); + max-width: calc(var(--slabs-content-max) + 2 * var(--slabs-gutter)); + padding: calc(var(--slabs-space-base) * 4) var(--slabs-gutter); +} + +.md-footer__link { + color: var(--slabs-calcaire); + opacity: 1; +} + +.md-footer__link:is(:focus, :hover) { + opacity: 1; +} + +.md-footer__link:hover .md-footer__title { + text-decoration: underline 2px var(--slabs-orange); + text-underline-offset: 0.2em; +} + +.md-footer__link:focus-visible { + outline-color: var(--slabs-orange); +} + +.md-footer__direction { + color: var(--slabs-beton); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + letter-spacing: var(--slabs-label-tracking); + text-transform: uppercase; +} + +.md-footer__title { + font-size: var(--slabs-body); + font-weight: 600; +} + +.md-footer-meta { + background-color: var(--slabs-anthracite); +} + +.md-footer-meta__inner { + max-width: calc(var(--slabs-content-max) + 2 * var(--slabs-gutter)); + padding: calc(var(--slabs-space-base) * 8) var(--slabs-gutter); +} + +.sl-footer { + display: grid; + gap: calc(var(--slabs-space-base) * 6); + grid-template-columns: auto 1fr; + width: 100%; +} + +/* The signature: the S at 40 px at least, which makes the file 60 px high. */ +.sl-footer__signature img { + display: block; + height: 60px; + width: auto; +} + +.md-typeset ul.sl-footer__links > li { + list-style: none; + margin: 0; +} + +.md-typeset ul.sl-footer__links:not([hidden]) { + align-self: start; + display: flex; + justify-content: flex-end; + flex-wrap: wrap; + gap: calc(var(--slabs-space-base) * 2) calc(var(--slabs-space-base) * 6); + list-style: none; + margin: 0; + padding: 0; +} + +.sl-footer__links a, +.md-footer-meta .sl-footer__links a { + color: var(--slabs-calcaire); + font-size: var(--slabs-nav); + text-decoration: underline var(--slabs-link-underline) var(--slabs-beton); + text-underline-offset: 0.2em; +} + +.sl-footer__legal { + border-top: 1px solid var(--slabs-schiste); + color: var(--slabs-beton); + font-family: var(--md-code-font-family); + font-size: var(--slabs-caption); + grid-column: 1 / -1; + margin: 0; + padding-top: calc(var(--slabs-space-base) * 4); +} + +.sl-footer__legal a, +.md-footer-meta .sl-footer__legal a { + color: var(--slabs-beton); + text-decoration: none; +} + +.md-footer-meta :is(.sl-footer__links, .sl-footer__legal) a:is(:focus, :hover) { + color: var(--slabs-calcaire); + text-decoration: underline 2px var(--slabs-orange); + text-underline-offset: 0.2em; +} + +.md-footer-meta a:focus-visible { + outline-color: var(--slabs-orange); +} + +@media screen and (max-width: 640px) { + .sl-footer { + grid-template-columns: 1fr; + } + + .md-typeset ul.sl-footer__links:not([hidden]) { + justify-content: flex-start; + } +} diff --git a/migration/from-v5.x-to-v6.0.md b/docs/migration/from-v5.x-to-v6.0.md similarity index 85% rename from migration/from-v5.x-to-v6.0.md rename to docs/migration/from-v5.x-to-v6.0.md index b9c0c6b..8a2169d 100644 --- a/migration/from-v5.x-to-v6.0.md +++ b/docs/migration/from-v5.x-to-v6.0.md @@ -4,9 +4,9 @@ description: Step-by-step guide for migrating from 5.x to 6.0 # From 5.x to 6.0 -{% hint style="info" %} -This page is subject to changes as the version 6.0.0 is not available at the time of writing. -{% endhint %} +!!! info + + This page is subject to changes as the version 6.0.0 is not available at the time of writing. This project follows the [Semantic Versioning principles](https://semver.org) and, contrary to upgrading a minor version (where the middle number changes) where no difficulty should be encountered, upgrading a major version (where the first number changes) is subject to significant modifications. @@ -18,9 +18,9 @@ This project follows the [Semantic Versioning principles](https://semver.org) an ### PublicKeyCredentialRpEntity.name -{% hint style="warning" %} -**Deprecated in v5.3.0** -{% endhint %} +!!! warning + + **Deprecated in v5.3.0** The `name` property of `PublicKeyCredentialRpEntity` is deprecated in version 5.3.0 and will be removed in version 6.0.0. According to the WebAuthn Level 3 specification, the Relying Party name is no longer required. @@ -32,9 +32,9 @@ $rpEntity = PublicKeyCredentialRpEntity::create('My Application', 'example.com') $rpEntity = PublicKeyCredentialRpEntity::create('', 'example.com'); ``` -{% hint style="info" %} -**Changed in v5.3.7:** the deprecation is carried by `PublicKeyCredentialRpEntity` only. `PublicKeyCredentialUserEntity.name` is **not** deprecated: the property moves from the abstract parent class down to the user entity in 6.0.0 and keeps working exactly as before. Until 5.3.6 the tag sat on the parent class, so static analysis reported every read of a user entity name as deprecated. -{% endhint %} +!!! info + + **Changed in v5.3.7:** the deprecation is carried by `PublicKeyCredentialRpEntity` only. `PublicKeyCredentialUserEntity.name` is **not** deprecated: the property moves from the abstract parent class down to the user entity in 6.0.0 and keeps working exactly as before. Until 5.3.6 the tag sat on the parent class, so static analysis reported every read of a user entity name as deprecated. The `name` member is required by the W3C IDL. Since v5.3.7 the serializer writes the Relying Party ID in that member when the entity has an empty name, so the payload sent to the browser stays valid without any action on your side. @@ -52,9 +52,9 @@ Between 5.3.3 and 5.3.6 the bundle copied `rp.id` into `rp.name` on the entity i ### PublicKeyCredentialSource -{% hint style="warning" %} -**Deprecated in v5.3.0** -{% endhint %} +!!! warning + + **Deprecated in v5.3.0** The class `Webauthn\PublicKeyCredentialSource` has been renamed to `Webauthn\CredentialRecord` to better reflect its purpose. The old class now extends `CredentialRecord` for backward compatibility but will be removed in version 6.0.0. @@ -72,15 +72,15 @@ $credential = new CredentialRecord(/* ... */); Similarly, the repository interface `PublicKeyCredentialSourceRepositoryInterface` is deprecated in favor of `CredentialRecordRepositoryInterface`. -{% hint style="info" %} -**Fixed in v5.3.7:** the bundle aliases the deprecated `PublicKeyCredentialSourceRepositoryInterface` to your repository only when that repository actually implements it. Symfony refuses an interface alias whose target does not implement the interface, so a repository following the deprecation and implementing `CredentialRecordRepositoryInterface` alone made the container invalid. Applications whose repository still implements the legacy interface keep the alias, and code type-hinting it keeps working. -{% endhint %} +!!! info + + **Fixed in v5.3.7:** the bundle aliases the deprecated `PublicKeyCredentialSourceRepositoryInterface` to your repository only when that repository actually implements it. Symfony refuses an interface alias whose target does not implement the interface, so a repository following the deprecation and implementing `CredentialRecordRepositoryInterface` alone made the container invalid. Applications whose repository still implements the legacy interface keep the alias, and code type-hinting it keeps working. ### DoctrineCredentialSourceRepository -{% hint style="warning" %} -**Deprecated in v5.2.0** -{% endhint %} +!!! warning + + **Deprecated in v5.2.0** The class `Webauthn\Bundle\Repository\DoctrineCredentialSourceRepository` provided by the Symfony bundle is deprecated and will be removed in version 6.0.0. You should create your own Doctrine-based repository instead. @@ -105,17 +105,17 @@ See the [Credential Record Repository](../symfony-bundle/credential-record-repos ### createFormJson -{% hint style="warning" %} -**Deprecated in v5.3.0** -{% endhint %} +!!! warning + + **Deprecated in v5.3.0** The `createFormJson` method is deprecated and will be removed in version 6.0.0. Use the standard Symfony Serializer to deserialize credential responses instead. ### Composer package `web-auth/webauthn-stimulus` -{% hint style="warning" %} -**Deprecated in v5.3.0 — removed in v6.0.0** -{% endhint %} +!!! warning + + **Deprecated in v5.3.0 — removed in v6.0.0** The dedicated PHP package `web-auth/webauthn-stimulus` (the Symfony Flex/AssetMapper wrapper around the Stimulus controllers) is deprecated. The same JavaScript is now published to npm as [`@web-auth/webauthn-stimulus`](https://www.npmjs.com/package/@web-auth/webauthn-stimulus) and that is the only package that will keep being maintained in 6.0.0. @@ -146,17 +146,17 @@ app.register('web-auth--webauthn-stimulus--registration', RegistrationController app.register('web-auth--webauthn-stimulus', WebauthnController); ``` -{% hint style="danger" %} -**Do not add `@web-auth/webauthn-stimulus` to `assets/controllers.json`.** Symfony UX `StimulusBundle` resolves `controllers.json` entries against installed Composer packages, so it throws `Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json` once the Composer wrapper is gone. Register from JavaScript instead, as shown above. -{% endhint %} +!!! danger + + **Do not add `@web-auth/webauthn-stimulus` to `assets/controllers.json`.** Symfony UX `StimulusBundle` resolves `controllers.json` entries against installed Composer packages, so it throws `Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json` once the Composer wrapper is gone. Register from JavaScript instead, as shown above. Your Twig templates do not need any change — `stimulus_controller('@web-auth/webauthn-stimulus/authentication')` still resolves to the `web-auth--webauthn-stimulus--authentication` identifier you just registered. -### Authenticator Transport CABLE +### Authenticator transport CABLE -{% hint style="warning" %} -**Deprecated in v5.3.0** -{% endhint %} +!!! warning + + **Deprecated in v5.3.0** The constant `AUTHENTICATOR_TRANSPORT_CABLE` is deprecated and will be removed in version 6.0.0. Use `AUTHENTICATOR_TRANSPORT_HYBRID` (the spec-aligned successor for caBLE / cloud-assisted BLE) instead. @@ -172,11 +172,11 @@ use Webauthn\PublicKeyCredentialDescriptor; $transport = PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_HYBRID; ``` -### New Authenticator Transports +### New authenticator transports + +!!! info -{% hint style="info" %} -**Added in v5.3.0** -{% endhint %} + **Added in v5.3.0** `PublicKeyCredentialDescriptor` exposes two new transport constants in addition to the historic `usb`, `nfc`, `ble` and `internal`: @@ -185,11 +185,11 @@ $transport = PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORT_HYBRID; All seven values are referenced by `PublicKeyCredentialDescriptor::AUTHENTICATOR_TRANSPORTS`. -### Options Handlers Signature +### Options handlers signature + +!!! warning -{% hint style="warning" %} -**Changed in v5.3.0** -{% endhint %} + **Changed in v5.3.0** The `CreationOptionsHandler` and `RequestOptionsHandler` interfaces now accept an optional `?Request $request` parameter. If you implement these interfaces, you must update the signature of your methods. @@ -257,7 +257,7 @@ webauthn: allow_subdomains: false ``` -### Options Storage +### Options storage `options_storage` option on the controller or firewall levels are deprecated. Please use the top level configuration key diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 0000000..8de097c --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{#- + The favicons and the share image of the design kit (4-Web/icones, 4-Web/partage). The SVG favicon is + theme.favicon; the ICO and the Apple touch icon complete it. +-#} +{% block extrahead %} + + + + + {% set title = (page.title ~ " · " ~ config.site_name) if page and page.title and not page.is_homepage else config.site_name %} + {% set description = (page.meta.description if page and page.meta and page.meta.description else config.site_description) %} + + + + + {% if page and page.canonical_url %}{% endif %} + + + + + +{% endblock %} diff --git a/docs/overrides/partials/copyright.html b/docs/overrides/partials/copyright.html new file mode 100644 index 0000000..7fe0bb4 --- /dev/null +++ b/docs/overrides/partials/copyright.html @@ -0,0 +1,22 @@ +{#- + The footer of the Slabs guidelines: the reversed signature, the links in Calcaire, the legal line in Béton. + The signature is the drawing of the design kit, never retyped. Its links and the legal line are in + extra.slabs of mkdocs.yml. +-#} +{% set slabs = config.extra.slabs %} + diff --git a/docs/overrides/partials/logo.html b/docs/overrides/partials/logo.html new file mode 100644 index 0000000..e261fcf --- /dev/null +++ b/docs/overrides/partials/logo.html @@ -0,0 +1,8 @@ +{#- + The project mark of WebAuthn Framework: the Slabs monogram, the name of the project and "by Slabs". The + colour version on a light ground, the reversed one on a dark ground, from the web set of the design kit + without their metadata (tools/web-svg.py of Spomky-Labs/docs). The link around them carries the + accessible name, so the images say nothing. +-#} + + diff --git a/docs/overrides/partials/source.html b/docs/overrides/partials/source.html new file mode 100644 index 0000000..3d8f7f0 --- /dev/null +++ b/docs/overrides/partials/source.html @@ -0,0 +1,13 @@ +{#- + The link to the repository, without the stars and the forks: the theme would ask the API of GitHub for + them from the browser of the reader, and this site sends nothing to a third party. +-#} + +
+ {% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %} + {% include ".icons/" ~ icon ~ ".svg" %} +
+
+ {{ config.repo_name }} +
+
diff --git a/prerequisites/credential-record.md b/docs/prerequisites/credential-record.md similarity index 76% rename from prerequisites/credential-record.md rename to docs/prerequisites/credential-record.md index f05a996..8c766d5 100644 --- a/prerequisites/credential-record.md +++ b/docs/prerequisites/credential-record.md @@ -2,9 +2,9 @@ description: Authenticator details and how to manage them --- -# Credential Record +# Credential record -## Credential Record Class +## Credential record class After the registration of an authenticator, you will get a `Webauthn\CredentialRecord` object. This object contains all the credential data needed to perform user authentication: @@ -22,12 +22,11 @@ After the registration of an authenticator, you will get a `Webauthn\CredentialR * **uvInitialized**: Whether user verification was initialized (since 5.1) * **otherUI**: Optional additional UI hints (array) -{% hint style="warning" %} -**Renamed in v5.3.0:** The class `Webauthn\PublicKeyCredentialSource` has been renamed to `Webauthn\CredentialRecord`. The old class name is deprecated and will be removed in version 6.0. `PublicKeyCredentialSource` now extends `CredentialRecord` for backward compatibility. -{% endhint %} +!!! warning -{% code lineNumbers="true" %} -```php + **Renamed in v5.3.0:** The class `Webauthn\PublicKeyCredentialSource` has been renamed to `Webauthn\CredentialRecord`. The old class name is deprecated and will be removed in version 6.0. `PublicKeyCredentialSource` now extends `CredentialRecord` for backward compatibility. + +```php linenums="1" transports; // ['usb', 'nfc'] // Get the descriptor for authentication $descriptor = $credentialRecord->getPublicKeyCredentialDescriptor(); ``` -{% endcode %} -## Credential Record Repository +## Credential record repository Since 4.6.0 and except if you use the Symfony bundle, there is no interface to implement or abstract class to extend, making it easy to integrate into your application. @@ -61,16 +59,15 @@ Your repository needs to provide two main operations: 1. **Save a credential** after registration or counter update 2. **Find credentials** by credential ID or user handle -{% hint style="success" %} -Whatever database you use (MySQL, PostgreSQL, MongoDB…), it is not necessary to create foreign key relationships between your users and the Credential Records. The `userHandle` property is sufficient to link credentials to users. -{% endhint %} +!!! success + + Whatever database you use (MySQL, PostgreSQL, MongoDB…), it is not necessary to create foreign key relationships between your users and the Credential Records. The `userHandle` property is sufficient to link credentials to users. -### Repository Example +### Repository example Here's a simple example using an array storage (for demonstration purposes): -{% code lineNumbers="true" %} -```php +```php linenums="1" add(PackedAttestationStatementSupport::crea use Webauthn\AttestationStatement\CompoundAttestationStatementSupport; $attestationStatementSupportManager->add(CompoundAttestationStatementSupport::create($attestationStatementSupportManager)); ``` -{% endcode %} -{% hint style="warning" %} -**Changed in v5.3.6:** `AndroidKeyAttestationStatementSupport` verifies the authorization lists of the attestation certificate. The key must have been generated inside the keystore (`origin` equal to `KM_ORIGIN_GENERATED`) and be allowed to sign (`purpose` containing `KM_PURPOSE_SIGN`). The union of the `softwareEnforced` and `teeEnforced` lists is used, as described by the specification. An imported key, accepted by the previous versions, is now rejected. -{% endhint %} +!!! warning + + **Changed in v5.3.6:** `AndroidKeyAttestationStatementSupport` verifies the authorization lists of the attestation certificate. The key must have been generated inside the keystore (`origin` equal to `KM_ORIGIN_GENERATED`) and be allowed to sign (`purpose` containing `KM_PURPOSE_SIGN`). The union of the `softwareEnforced` and `teeEnforced` lists is used, as described by the specification. An imported key, accepted by the previous versions, is now rejected. -{% hint style="info" %} -**Compound Attestation Statement (v5.3.0+):** The Compound Attestation format allows multiple attestation statements to be bundled together. This is useful for authenticators that support multiple attestation mechanisms. The `CompoundAttestationStatementSupport` requires the `AttestationStatementSupportManager` to process the individual attestation statements within the compound format. -{% endhint %} +!!! info -{% hint style="warning" %} -The Android SafetyNet Attestation API is deprecated. Full turndown is planned in June 2024. More information at [https://developer.android.com/training/safetynet/deprecation-timeline](https://developer.android.com/training/safetynet/deprecation-timeline) + **Compound Attestation Statement (v5.3.0+):** The Compound Attestation format allows multiple attestation statements to be bundled together. This is useful for authenticators that support multiple attestation mechanisms. The `CompoundAttestationStatementSupport` requires the `AttestationStatementSupportManager` to process the individual attestation statements within the compound format. -It is not described on this page, but available in the previous versions of the documentation. -{% endhint %} +!!! warning -## Metadata Statement Repository + The Android SafetyNet Attestation API is deprecated. Full turndown is planned in June 2024. More information at [https://developer.android.com/training/safetynet/deprecation-timeline](https://developer.android.com/training/safetynet/deprecation-timeline) + + It is not described on this page, but available in the previous versions of the documentation. + +## Metadata statement repository Then, you must prepare a Metadata Statement Repository. This service will manage all Metadata Statements depending on their sources (local storage or distant service). @@ -83,11 +81,11 @@ Your Metadata Statement Repository must implement the interface `Webauthn\Metada * `findOneByAAGUID(string $aaguid)`: this method retrieves the `MetadataStatement` object with AAGUID. It shall return `null` in case of the absence of the MDS. -{% hint style="warning" %} -The library does not provide any Metadata Statement Repository. It is up to you to select the MDS suitable for your application and store them in your database. -{% endhint %} +!!! warning + + The library does not provide any Metadata Statement Repository. It is up to you to select the MDS suitable for your application and store them in your database. -## Status Report Repository +## Status report repository To prevent the use of rogue MDS or deprecated by the manufacturers, Status Reports may be used. The Status Report Repository is a simple service that implements the interface `Webauthn\MetadataService\StatusReportRepository` with a unique method: @@ -95,14 +93,13 @@ To prevent the use of rogue MDS or deprecated by the manufacturers, Status Repor The verification's success or failure depends on the state reported in the most recent Status Report. -## Certificate Chain Validator +## Certificate chain validator When an attestation statement is received, a certificate chain is constructed. This chain combines certificates from the Metadata Service (MDS), which are trusted, with those provided by the authenticator, which are untrusted. The Certificate Chain Validator is responsible for this task. The library provides the class `Webauthn\MetadataService\CertificateChain\PhpCertificateChainValidator` that requires a Symfony Http Client ([for CRL verification](https://en.wikipedia.org/wiki/Certificate_revocation_list)) and a PSR-20 Clock. -{% code lineNumbers="true" %} -```php +```php linenums="1" enableMetadataStatementSupport( $certificateChainValidator, ); ``` -{% endcode %} -## Requesting Attestation Statement +## Requesting attestation statement By default, no Attestation Statement is asked to the Authenticators (type = `none`). To change this behavior, you just have to set the corresponding parameter in the `Webauthn\PublicKeyCredentialCreationOptions` object. @@ -146,8 +140,7 @@ There are 3 conveyance modes available using PHP constants provided by the class * `ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT`: the Relying Party prefers an attestation conveyance yielding verifiable attestation statements, but allows the client to decide how to obtain such attestation statements. * `ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT`: the Relying Party wants to receive the attestation statement as generated by the authenticator. -{% code lineNumbers="true" %} -```php +```php linenums="1" setAlgorithmManager($algorithmManager); ``` -{% endcode %} diff --git a/pure-php/advanced-behaviours/authenticator-counter.md b/docs/pure-php/advanced-behaviours/authenticator-counter.md similarity index 92% rename from pure-php/advanced-behaviours/authenticator-counter.md rename to docs/pure-php/advanced-behaviours/authenticator-counter.md index d846207..84f62d2 100644 --- a/pure-php/advanced-behaviours/authenticator-counter.md +++ b/docs/pure-php/advanced-behaviours/authenticator-counter.md @@ -1,4 +1,4 @@ -# Authenticator Counter +# Authenticator counter The authenticators may have an internal counter. This feature is very helpful to detect cloned devices. @@ -8,13 +8,11 @@ It is therefore required to go deeper in the protection of your application by l To do so, you have to create a custom Counter Checker and inject it into your Authenticator Assertion Response Validator. The checker must implement the interface `Webauthn\Counter\CounterChecker`. -{% code lineNumbers="true" %} -```php +```php linenums="1" setCounterChecker($customCounterChecker); ``` -{% endcode %} diff --git a/pure-php/advanced-behaviours/authenticator-selection-criteria.md b/docs/pure-php/advanced-behaviours/authenticator-selection-criteria.md similarity index 79% rename from pure-php/advanced-behaviours/authenticator-selection-criteria.md rename to docs/pure-php/advanced-behaviours/authenticator-selection-criteria.md index 2996641..e46532f 100644 --- a/pure-php/advanced-behaviours/authenticator-selection-criteria.md +++ b/docs/pure-php/advanced-behaviours/authenticator-selection-criteria.md @@ -1,4 +1,4 @@ -# Authenticator Selection Criteria +# Authenticator selection criteria By default, any type of authenticator can be used by your users and interact with your application. In certain circumstances, you may need to select specific authenticators e.g. when user verification is required. @@ -6,9 +6,9 @@ The WebAuthn API and this library allow you to define a set of options to disall The class `Webauthn\AuthenticatorSelectionCriteria` is designed for this purpose. It is used when generating the `Webauthn\PublicKeyCredentialCreationOptions` object. -## Available Criteria +## Available criteria -### Authenticator Attachment Modality +### Authenticator attachment modality You can indicate if the authenticator must be attached to the client (platform authenticator i.e. it is usually not removable from the client device) or must be detached (roaming authenticator). @@ -20,19 +20,19 @@ Possible values are: A primary use case for platform authenticators is to register a particular client device as a "trusted device" for future authentication. This gives the user the convenience benefit of not needing a roaming authenticator, e.g., the user will not have to dig around in their pocket for their key fob or phone. -### Resident Key +### Resident key With this criterion, a credential record will be stored in the authenticator, client or client device. Such storage requires an authenticator capable to store such a resident credential. -{% hint style="info" %} -A resident key shall be created if you want to [authenticate users without username](authentication-without-username.md). -{% endhint %} +!!! info -{% hint style="warning" %} -**Backward Compatibility (v5.3.0+):** The `requireResidentKey` property has been restored for backward compatibility with WebAuthn Level 3 specification. While `residentKey` is the preferred modern approach, `requireResidentKey` is still supported for legacy implementations. -{% endhint %} + A resident key shall be created if you want to [authenticate users without username](authentication-without-username.md). -### User Verification +!!! warning + + **Backward Compatibility (v5.3.0+):** The `requireResidentKey` property has been restored for backward compatibility with WebAuthn Level 3 specification. While `residentKey` is the preferred modern approach, `requireResidentKey` is still supported for legacy implementations. + +### User verification [Please refer to this page](user-verification.md). @@ -40,8 +40,7 @@ A resident key shall be created if you want to [authenticate users without usern With this example, we require the user verification (PIN, fingerprint...), a resident key and an authenticator embedded onto a device. This is typically what you will require for Windows Hello or Face ID authentication. -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\PublicKeyCredentialCreationOptions; @@ -59,4 +58,3 @@ $publicKeyCredentialCreationOptions = authenticatorSelection: $authenticatorSelectionCriteria ); ``` -{% endcode %} diff --git a/pure-php/advanced-behaviours/backup-events.md b/docs/pure-php/advanced-behaviours/backup-events.md similarity index 82% rename from pure-php/advanced-behaviours/backup-events.md rename to docs/pure-php/advanced-behaviours/backup-events.md index dac2029..02181ad 100644 --- a/pure-php/advanced-behaviours/backup-events.md +++ b/docs/pure-php/advanced-behaviours/backup-events.md @@ -1,8 +1,8 @@ -# Backup Events +# Backup events -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} +!!! info + + **New in v5.3.0** The framework dispatches PSR-14 events when the backup eligibility or backup status flags of a credential change during authentication. These events allow you to react to changes in credential backup state, which is important for security monitoring and user guidance. @@ -13,9 +13,9 @@ WebAuthn authenticators report two backup-related flags: * **BE (Backup Eligible)**: Indicates whether the authenticator is capable of backing up the credential (e.g., synced passkeys via iCloud Keychain, Google Password Manager) * **BS (Backup Status)**: Indicates whether the credential is currently backed up -{% hint style="info" %} -**Fixed in v5.3.6:** `AuthenticatorData::getReservedForFutureUse2()` reported the bits 3 and 4 as reserved, although WebAuthn Level 3 assigns them to BE and BS. A synced passkey with both flags set returned `24` instead of `0`. The mask is now restricted to the only bit that is still reserved. Use `isBackupEligible()` and `isBackedUp()` to read the backup flags. -{% endhint %} +!!! info + + **Fixed in v5.3.6:** `AuthenticatorData::getReservedForFutureUse2()` reported the bits 3 and 4 as reserved, although WebAuthn Level 3 assigns them to BE and BS. A synced passkey with both flags set returned `24` instead of `0`. The mask is now restricted to the only bit that is still reserved. Use `isBackupEligible()` and `isBackedUp()` to read the backup flags. Changes in these flags can signal important security events: @@ -28,8 +28,7 @@ Changes in these flags can signal important security events: Dispatched when the backup eligibility (BE) flag changes between authentications. -{% code lineNumbers="true" %} -```php +```php linenums="1" addListener( new BackupStatusListener() ); ``` -{% endcode %} ### Symfony With the Symfony bundle, register your listeners as services. With autoconfiguration, they are registered automatically. -{% code title="config/services.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/services.yaml" linenums="1" services: App\EventListener\BackupStatusListener: tags: @@ -122,15 +115,14 @@ services: tags: - { name: kernel.event_listener, event: Webauthn\Event\BackupEligibilityChangedEvent } ``` -{% endcode %} -## Security Considerations +## Security considerations * **Monitor backup status loss**: If a credential transitions from backed up to not backed up, the user may have lost their backup. Consider prompting them to register additional authenticators. * **Audit backup eligibility changes**: Unexpected changes in backup eligibility may indicate authenticator changes worth logging. * **Do not block authentication**: These events are informational. Do not reject authentication attempts based on backup flag changes. -## See Also +## See also * [Credential Record](../../prerequisites/credential-record.md) - Credential properties including backup flags * [Authenticator Counter](authenticator-counter.md) - Another security monitoring mechanism diff --git a/pure-php/advanced-behaviours/conditional-create.md b/docs/pure-php/advanced-behaviours/conditional-create.md similarity index 77% rename from pure-php/advanced-behaviours/conditional-create.md rename to docs/pure-php/advanced-behaviours/conditional-create.md index c722cbd..6c29dcd 100644 --- a/pure-php/advanced-behaviours/conditional-create.md +++ b/docs/pure-php/advanced-behaviours/conditional-create.md @@ -1,20 +1,20 @@ -# Conditional Create +# Conditional create -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} +!!! info + + **New in v5.3.0** Conditional Create allows you to register a WebAuthn credential without explicit user interaction, typically after a user has already authenticated via another method (e.g., password login). This enables a seamless upgrade path from traditional authentication to passkeys. -## How It Works +## How it works In a standard WebAuthn registration ceremony, user presence is always required (the user must interact with the authenticator). With Conditional Create (`mediation: 'conditional'`), the browser can silently create a credential after the user has already proven their identity through another means. Because the credential is created without any interaction, the User Present (UP) and User Verified (UV) flags of the authenticator data may both be `false`. The validation relaxes the two checks accordingly. -{% hint style="info" %} -**Changed in v5.3.6:** the User Verification check is relaxed as well. Until then only the User Presence check was, so a conditional registration made against a profile requiring user verification was rejected with `User authentication required.`. -{% endhint %} +!!! info + + **Changed in v5.3.6:** the User Verification check is relaxed as well. Until then only the User Presence check was, so a conditional registration made against a profile requiring user verification was rejected with `User authentication required.`. This is particularly useful for: @@ -22,16 +22,15 @@ This is particularly useful for: * **Progressive enrollment**: Gradually migrate users from passwords to passkeys * **Background registration**: Register credentials without interrupting the user flow -## Pure PHP Usage +## Pure PHP usage There are two equivalent ways to relax the User Presence (UP) and User Verification (UV) checks during validation: a per-request hint on the options, or a dedicated ceremony manager. The first option is the recommended one since v5.3.0 because the hint travels with the options across the storage round-trip and applies automatically. -### Option 1 — Set `mediation` on the options (recommended) +### Option 1 — set `mediation` on the options (recommended) `PublicKeyCredentialCreationOptions` exposes two constants, `MEDIATION_DEFAULT` and `MEDIATION_CONDITIONAL`, and a nullable `$mediation` property. When the property is set to `MEDIATION_CONDITIONAL`, `CheckUserWasPresent` skips the UP check and `CheckUserVerification` skips the UV check at runtime, regardless of which ceremony manager you use. -{% code lineNumbers="true" %} -```php +```php linenums="1" creationCeremony(); // Conditional create ceremony (user presence can be false) $conditionalCeremony = $csmFactory->conditionalCreateCeremony(); ``` -{% endcode %} Use the conditional ceremony manager when validating attestation responses from conditional create flows: -{% code lineNumbers="true" %} -```php +```php linenums="1" check( 'example.com' ); ``` -{% endcode %} -## Symfony Bundle Configuration +## Symfony bundle configuration Enable conditional create per creation profile. The bundle's `CeremonyStepManagerFactory` translates the `conditional_create: true` flag into `mediation = 'conditional'` on the produced options, so existing 5.3.x configurations behave identically: -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: creation_profiles: passkey_upgrade: @@ -112,12 +105,10 @@ webauthn: require_resident_key: true user_verification: preferred ``` -{% endcode %} If you would rather decide per request — for example, only enable conditional create when the JavaScript client explicitly asks for it — opt in via the [Client Override Policy](../../symfony-bundle/advanced-behaviors/client-override-policy.md): -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: creation_profiles: passkey_upgrade: @@ -128,20 +119,17 @@ webauthn: enabled: true allowed_values: ['default', 'conditional'] ``` -{% endcode %} -## Frontend Integration +## Frontend integration On the frontend, use `mediation: 'conditional'` when calling `navigator.credentials.create()`: -{% code lineNumbers="true" %} -```javascript +```javascript linenums="1" const credential = await navigator.credentials.create({ publicKey: creationOptions, mediation: 'conditional' }); ``` -{% endcode %} With the Stimulus controller, use the `autoRegister` option on the registration controller: @@ -153,11 +141,11 @@ With the Stimulus controller, use the `autoRegister` option on the registration }) }} ``` -{% hint style="warning" %} -Conditional Create requires browser support. Not all browsers support this feature yet. Always provide a fallback registration flow. -{% endhint %} +!!! warning + + Conditional Create requires browser support. Not all browsers support this feature yet. Always provide a fallback registration flow. -## See Also +## See also * [Register Authenticators](../../pure-php/authenticator-registration.md) - Standard registration ceremony * [Stimulus Controllers](../../symfony-ux/installation.md) - Frontend integration diff --git a/pure-php/advanced-behaviours/cross-origin-authentication.md b/docs/pure-php/advanced-behaviours/cross-origin-authentication.md similarity index 70% rename from pure-php/advanced-behaviours/cross-origin-authentication.md rename to docs/pure-php/advanced-behaviours/cross-origin-authentication.md index cad6998..3ee06f2 100644 --- a/pure-php/advanced-behaviours/cross-origin-authentication.md +++ b/docs/pure-php/advanced-behaviours/cross-origin-authentication.md @@ -1,4 +1,4 @@ -# Cross Origin Authentication +# Cross origin authentication ## Default behavior @@ -9,22 +9,21 @@ By default, the current host is used, and the origin parameter returned in the W * **Subdomain authentication**: Some deployments use a wildcard subdomain approach (e.g., `tenant1.example.com`, `tenant2.example.com`), but strict origin checking will reject authentication if the relying party ID does not match exactly. * **Development and testing environments**: Local development often uses `http://localhost`, which is not allowed by default due to the HTTPS requirement. -## Customizing Allowed Origins +## Customizing allowed origins To handle these cases, you can explicitly define a list of allowed origins when configuring the WebAuthn authentication process using the method `setAllowedOrigins`. -{% hint style="info" %} -The method `setSecuredRelyingPartyId` is deprecated since 5.2.0 -{% endhint %} +!!! info -### Allowing Multiple Origins + The method `setSecuredRelyingPartyId` is deprecated since 5.2.0 + +### Allowing multiple origins If your application supports multiple domains, specify them explicitly: This ensures that authentication requests coming from any of these origins are accepted. -{% code lineNumbers="true" %} -```php +```php linenums="1" setAllowedOrigins([ 'https://old-acme.com' ]); ``` -{% endcode %} -### Allowing Subdomains +### Allowing subdomains To allow authentication from subdomains dynamically, enable the `allowSubdomains` flag: -{% code lineNumbers="true" %} -```php +```php linenums="1" setAllowedOrigins(['https://acme.com'], true); ``` -{% endcode %} With this setting, authentication requests from `https://sub.acme.com` will be considered valid. -{% hint style="danger" %} -Do not use TLD and the sub domain flag together! `$csmFactory->setSecuredRelyingPartyId(['com'], true);` -{% endhint %} +!!! danger + + Do not use TLD and the sub domain flag together! `$csmFactory->setSecuredRelyingPartyId(['com'], true);` -### Supporting Native Applications +### Supporting native applications For native applications, origins are different from traditional web URLs. If your application integrates with mobile apps, ensure that relevant origins are included: -{% code lineNumbers="true" %} -```php +```php linenums="1" setAllowedOrigins([ 'ios:bundle-id://your.bundle.id', ]); ``` -{% endcode %} This configuration allows authentication responses from web browsers and native applications. -{% hint style="info" %} -**Changed in v5.3.3:** an entry that is not a URL, i.e. that has a scheme but no host, such as `android:apk-key-hash:`, is stored as is and compared verbatim with the `origin` field of the client data. Entries made of a host only, such as `acme.com`, are still normalized to `https://acme.com` because WebAuthn requires TLS. +!!! info + + **Changed in v5.3.3:** an entry that is not a URL, i.e. that has a scheme but no host, such as `android:apk-key-hash:`, is stored as is and compared verbatim with the `origin` field of the client data. Entries made of a host only, such as `acme.com`, are still normalized to `https://acme.com` because WebAuthn requires TLS. -Copy the facet identifier exactly as the application sends it. Before v5.3.3 these entries went through the host normalization and were turned into unusable origins such as `https://android:apk-key-hash:...`, so they never matched. -{% endhint %} + Copy the facet identifier exactly as the application sends it. Before v5.3.3 these entries went through the host normalization and were turned into unusable origins such as `https://android:apk-key-hash:...`, so they never matched. -### Enabling `http://localhost` for Development +### Enabling `http://localhost` for development During local development, you may need to allow `http://localhost`. Since WebAuthn generally requires HTTPS, this is an exception for local testing. To allow it: -{% code lineNumbers="true" %} -```php +```php linenums="1" setAllowedOrigins([ 'http://localhost', ]); ``` -{% endcode %} Be aware that this should only be enabled in non-production environments. -## Top Origin Validation (Cross-Origin iframes) +## Top origin validation (cross-origin iframes) When the ceremony takes place in a cross-origin iframe, for example a page of `auth.example.com` embedded in `app.example.com`, the browser sets `crossOrigin` to `true` in the client data and adds the origin of the embedding page in a `topOrigin` field. -{% hint style="danger" %} -**Changed in v5.3.4:** a response that carries a `topOrigin` is rejected unless a `TopOriginValidator` is configured. Previous versions accepted such a response without checking anything, which means any site embedding your ceremony in an iframe was accepted. +!!! danger -Applications that do not use iframes have nothing to do: a same-origin ceremony has no `topOrigin` at all. Applications that are legitimately embedded must opt in with one of the validators below, otherwise their ceremonies now fail. -{% endhint %} + **Changed in v5.3.4:** a response that carries a `topOrigin` is rejected unless a `TopOriginValidator` is configured. Previous versions accepted such a response without checking anything, which means any site embedding your ceremony in an iframe was accepted. + + Applications that do not use iframes have nothing to do: a same-origin ceremony has no `topOrigin` at all. Applications that are legitimately embedded must opt in with one of the validators below, otherwise their ceremonies now fail. The step also rejects a response whose client data carries a `topOrigin` while `crossOrigin` is `false`, as the two fields contradict each other. -### Using the Built-in Host Validator +### Using the built-in host validator `HostTopOriginValidator` compares the received top origin with a fixed value: -{% code lineNumbers="true" %} -```php +```php linenums="1" enableTopOriginValidator( new HostTopOriginValidator('https://app.example.com') ); ``` -{% endcode %} -{% hint style="warning" %} -The comparison is strict. The expected value is the top origin as the browser sends it, i.e. a serialized origin such as `https://app.example.com`, and not a bare host name. -{% endhint %} +!!! warning + + The comparison is strict. The expected value is the top origin as the browser sends it, i.e. a serialized origin such as `https://app.example.com`, and not a bare host name. -### Using a Custom Validator +### Using a custom validator When several parent origins are allowed, implement the `TopOriginValidator` interface: -{% code lineNumbers="true" %} -```php +```php linenums="1" enableTopOriginValidator( new AllowedTopOriginsValidator([ 'https://app.example.com', @@ -196,11 +183,10 @@ $csmFactory->enableTopOriginValidator( ]) ); ``` -{% endcode %} A validator set on the factory applies to both the creation and the request ceremonies. `disableTopOriginValidator()` removes it, which is useful when the same factory is reused to build a manager for a context where iframes are not allowed. -## Security Considerations +## Security considerations When modifying allowed origins, ensure that: diff --git a/pure-php/advanced-behaviours/debugging.md b/docs/pure-php/advanced-behaviours/debugging.md similarity index 88% rename from pure-php/advanced-behaviours/debugging.md rename to docs/pure-php/advanced-behaviours/debugging.md index dfae766..377be3f 100644 --- a/pure-php/advanced-behaviours/debugging.md +++ b/docs/pure-php/advanced-behaviours/debugging.md @@ -2,16 +2,15 @@ WebAuthn operations can be complex to debug due to the cryptographic operations and browser interactions involved. This page explains how to enable logging to troubleshoot issues during development or monitor your application in production. -## PSR-3 Logger Integration +## PSR-3 logger integration The WebAuthn library supports [PSR-3 compatible loggers](https://www.php-fig.org/psr/psr-3/) for debugging and monitoring. You can use any PSR-3 logger implementation such as Monolog, Symfony Logger, or others. -### Setting Up a Logger +### Setting up a logger Several WebAuthn classes implement the `Webauthn\MetadataService\CanLogData` interface, allowing you to inject a PSR-3 logger: -{% code lineNumbers="true" %} -```php +```php linenums="1" setLogger($logger); $assertionValidator = AuthenticatorAssertionResponseValidator::create($requestCSM); $assertionValidator->setLogger($logger); ``` -{% endcode %} -## What Gets Logged +## What gets logged The WebAuthn library logs various events during authentication and registration: -### Registration (Attestation) Events +### Registration (attestation) events * Challenge generation and validation * Client data validation @@ -49,7 +47,7 @@ The WebAuthn library logs various events during authentication and registration: * Certificate chain validation (when using attestation) * Errors during any step -### Authentication (Assertion) Events +### Authentication (assertion) events * Challenge validation * Credential lookup @@ -58,7 +56,7 @@ The WebAuthn library logs various events during authentication and registration: * User presence and verification checks * Errors during any step -## Log Levels +## Log levels The library uses standard PSR-3 log levels: @@ -68,7 +66,7 @@ The library uses standard PSR-3 log levels: * **ERROR**: Error messages for failures * **CRITICAL**: Critical errors requiring immediate attention -## Example Log Output +## Example log output Here's what typical log messages look like: @@ -86,14 +84,13 @@ In case of errors: [2025-01-15 10:31:12] webauthn.CRITICAL: Signature verification failed ``` -## Development vs Production +## Development vs production -### Development Environment +### Development environment Enable verbose DEBUG logging to see all operations: -{% code lineNumbers="true" %} -```php +```php linenums="1" pushHandler( new StreamHandler('php://stdout', LogLevel::DEBUG) ); ``` -{% endcode %} -### Production Environment +### Production environment In production, log only warnings and errors to avoid performance impact: -{% code lineNumbers="true" %} -```php +```php linenums="1" pushHandler( ) ); ``` -{% endcode %} -## Common Issues and Log Messages +## Common issues and log messages ### "Challenge mismatch" @@ -154,41 +148,39 @@ $logger->pushHandler( **Solution**: Check your Relying Party ID configuration matches your domain. -### "User not present" or "User not verified" +### "User not present" or "user not verified" **Cause**: User verification requirements not met. **Solution**: Check your authenticator selection criteria configuration. -## Additional Debugging Tools +## Additional debugging tools -### Browser Developer Tools +### Browser developer tools Modern browsers provide WebAuthn debugging in developer tools: * **Chrome/Edge**: DevTools → Application → WebAuthn * **Firefox**: about:config → search for "webauthn" -### Network Inspector +### Network inspector Monitor the network requests to see: * Options requests (registration/authentication) * Response data being sent back * HTTP errors or timeouts -### Test Authenticators +### Test authenticators Use virtual authenticators for testing: -{% code lineNumbers="true" %} -```javascript +```javascript linenums="1" // Chrome DevTools Protocol // Enable virtual authenticator in Chrome DevTools → Application → WebAuthn // Add Virtual Authenticator → Configure options ``` -{% endcode %} -## See Also +## See also * [Symfony Bundle Debugging](../../symfony-bundle/advanced-behaviors/debugging.md) - Framework-specific logging * [Authenticator Counter](authenticator-counter.md) - Detect cloned authenticators diff --git a/pure-php/advanced-behaviours/extensions.md b/docs/pure-php/advanced-behaviours/extensions.md similarity index 88% rename from pure-php/advanced-behaviours/extensions.md rename to docs/pure-php/advanced-behaviours/extensions.md index 89b3e65..73a469d 100644 --- a/pure-php/advanced-behaviours/extensions.md +++ b/docs/pure-php/advanced-behaviours/extensions.md @@ -2,8 +2,7 @@ The following example is totally fictive. We will add an extension input `loc=true` to the request option object. -{% code lineNumbers="true" %} -```php +```php linenums="1" add(new LocationExtensionOutputChecker()); $csmFactory = new CeremonyStepManagerFactory(); $csmFactory->setExtensionOutputCheckerHandler($eocHandler); ``` -{% endcode %} diff --git a/pure-php/advanced-behaviours/signal-api.md b/docs/pure-php/advanced-behaviours/signal-api.md similarity index 90% rename from pure-php/advanced-behaviours/signal-api.md rename to docs/pure-php/advanced-behaviours/signal-api.md index 850fdc7..c1fb99e 100644 --- a/pure-php/advanced-behaviours/signal-api.md +++ b/docs/pure-php/advanced-behaviours/signal-api.md @@ -1,12 +1,12 @@ # Signal API -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} +!!! info + + **New in v5.3.0** The WebAuthn Signal API allows relying parties to send signals to the client (browser/platform) about credential status. This helps clients maintain accurate credential metadata and improves the user experience by keeping passkey lists up to date. -## What Is the Signal API? +## What is the Signal API? When users manage their passkeys on the server side (removing credentials, updating profile information), the client platform may still display outdated information. The Signal API provides a standardized way to inform the client about: @@ -14,14 +14,13 @@ When users manage their passkeys on the server side (removing credentials, updat * Updated user details (name, display name) * Credentials that are no longer recognized by the server -## Signal Types +## Signal types ### AllAcceptedCredentials Informs the client about all credentials that the server currently recognizes for a given user. This allows the client to remove any credentials that are no longer valid. -{% code lineNumbers="true" %} -```php +```php linenums="1" serialize( // "allAcceptedCredentialIds": ["...", "..."] // } ``` -{% endcode %} -## Use Cases +## Use cases -### After Credential Deletion +### After credential deletion When a user removes a passkey from your application, send an `AllAcceptedCredentials` signal with the remaining credentials so the client can update its list. -### After Profile Update +### After profile update When a user changes their username or display name, send a `CurrentUserDetails` signal so the client displays the correct information in its passkey picker. -### During Authentication +### During authentication If an authentication attempt references a credential that doesn't exist in your database, send an `UnknownCredential` signal to help the client clean up orphaned passkeys. -## See Also +## See also * [WebAuthn Signal API Specification](https://github.com/nicovil/webauthn-signal-api) - W3C specification * [Credential Record](../../prerequisites/credential-record.md) - Credential storage diff --git a/pure-php/advanced-behaviours/user-verification.md b/docs/pure-php/advanced-behaviours/user-verification.md similarity index 85% rename from pure-php/advanced-behaviours/user-verification.md rename to docs/pure-php/advanced-behaviours/user-verification.md index d6a4088..6c91db1 100644 --- a/pure-php/advanced-behaviours/user-verification.md +++ b/docs/pure-php/advanced-behaviours/user-verification.md @@ -1,11 +1,10 @@ -# User Verification +# User verification You can indicate the user verification requirements during the ceremonies by setting the value in your options. ### Authenticator registration -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\PublicKeyCredentialCreationOptions; @@ -18,12 +17,10 @@ $publicKeyCredentialCreationOptions = ) ; ``` -{% endcode %} -### User Authentication +### User authentication -{% code lineNumbers="true" %} -```php +```php linenums="1" // Public Key Credential Request Options use Webauthn\PublicKeyCredentialRequestOptions; @@ -32,4 +29,3 @@ $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_DISCOURAGED ); ``` -{% endcode %} diff --git a/pure-php/authenticate-your-users.md b/docs/pure-php/authenticate-your-users.md similarity index 82% rename from pure-php/authenticate-your-users.md rename to docs/pure-php/authenticate-your-users.md index 4207b21..dede0b4 100644 --- a/pure-php/authenticate-your-users.md +++ b/docs/pure-php/authenticate-your-users.md @@ -1,8 +1,8 @@ -# Authenticate Your Users +# Authenticate your users During this step, your application will send a challenge to the list of registered devices of the user. The security token will resolve this challenge by adding information and digitally signing the data. -## Assertion Request +## Assertion request To perform a user authentication using a security device, you need to instantiate a `Webauthn\PublicKeyCredentialRequestOptions` object. @@ -20,19 +20,18 @@ Optionally, you can customize the following parameters: The `PublicKeyCredentialRequestOptions` object is designed to be easily serialized into a JSON object. This will ease the integration into an HTML page or through an API endpoint. -{% hint style="info" %} -The timeout default value is set to `null`. If you want to set a value, please read the following recommended behavior showed in the specification: +!!! info -* If the user verification is `discouraged`, timeout should be between 30 and 180 seconds -* If the user verification is `preferred` or `required`, the range is 300 to 600 seconds (5 to 10 minutes) -{% endhint %} + The timeout default value is set to `null`. If you want to set a value, please read the following recommended behavior showed in the specification: -### Allowed Credentials + * If the user verification is `discouraged`, timeout should be between 30 and 180 seconds + * If the user verification is `preferred` or `required`, the range is 300 to 600 seconds (5 to 10 minutes) + +### Allowed credentials The user trying to authenticate must have registered at least one device. For this user, you have to get all `Webauthn\PublicKeyCredentialDescriptor` associated to his account. -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\CredentialRecord; use Webauthn\PublicKeyCredentialDescriptor; @@ -50,20 +49,18 @@ $allowedCredentials = array_map( $registeredAuthenticators ); ``` -{% endcode %} -{% hint style="info" %} -For usernameless authentication, please read the [dedicated page](advanced-behaviours/authentication-without-username.md). In this case no Public Key Credential Descriptors should be passed to the options. -{% endhint %} +!!! info + + For usernameless authentication, please read the [dedicated page](advanced-behaviours/authentication-without-username.md). In this case no Public Key Credential Descriptors should be passed to the options. + +!!! danger -{% hint style="danger" %} -To prevent username enumeration, when the authentication process is performed using the username, it is highly recommended to continue the ceremony even if the user entity does not exist. The allowed credential list should be set with random data. -{% endhint %} + To prevent username enumeration, when the authentication process is performed using the username, it is highly recommended to continue the ceremony even if the user entity does not exist. The allowed credential list should be set with random data. ### Example -{% code lineNumbers="true" %} -```php +```php linenums="1" deserialize($data, PublicKeyCredential::class, 'json'); ``` -{% endcode %} -### Response Verification +### Response verification Now we have a fully loaded Public Key Credential object, but we need now to make sure that: @@ -201,8 +189,7 @@ Now we have a fully loaded Public Key Credential object, but we need now to make The first is easy to perform: -{% code lineNumbers="true" %} -```php +```php linenums="1" response instanceof AuthenticatorAssertionResponse) { //e.g. process here with a redirection to the public key login/MFA page. } ``` -{% endcode %} The second step is the verification against the Public Key Assertion Options we created earlier. The Authenticator Assertion Response Validator service (variable `$authenticatorAssertionResponseValidator`) will check everything for you. -{% code lineNumbers="true" %} -```php +```php linenums="1" check( $credentialRecordRepository->saveCredentialRecord($credentialRecord); ``` -{% endcode %} If no exception is thrown, the response is valid and you can continue the authentication of the user. diff --git a/pure-php/authenticator-registration.md b/docs/pure-php/authenticator-registration.md similarity index 82% rename from pure-php/authenticator-registration.md rename to docs/pure-php/authenticator-registration.md index 0fb65b6..023789c 100644 --- a/pure-php/authenticator-registration.md +++ b/docs/pure-php/authenticator-registration.md @@ -1,10 +1,10 @@ -# Register Authenticators +# Register authenticators During this step, your application will send a challenge to the device. The device will resolve this challenge by adding information and digitally signing the data. The application will check the response from the device and get its credential ID. This ID will be used for further authentication requests. -## Creation Request +## Creation request To associate a device to a user, you need to instantiate a `Webauthn\PublicKeyCredentialCreationOptions` object. @@ -25,8 +25,7 @@ Optionally, you can customize the following parameters: Let’s see an example of the `PublicKeyCredentialCreationOptions` object. The following example is a possible Public Key Creation page for a dummy user "@cypher-Angel-3000". -{% code lineNumbers="true" %} -```php +```php linenums="1" serialize( ); ``` -{% hint style="warning" %} -It is important to store the user entity and the options object (e.g. in the session) for the next step. The data will be needed to check the response from the device. -{% endhint %} +!!! warning + + It is important to store the user entity and the options object (e.g. in the session) for the next step. The data will be needed to check the response from the device. You can change the default values for each and all options -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\AuthenticatorSelectionCriteria; $publicKeyCredentialCreationOptions = @@ -98,16 +95,14 @@ $publicKeyCredentialCreationOptions = ) ; ``` -{% endcode %} -### Public Key Credential Parameters +### Public key credential parameters The argument `pubKeyCredParams` contains a list of `Webauthn\PublicKeyCredentialParameters` objects that refer to COSE algorithms. The authenticators must use one of the algorithms in this list, respecting the order of preference on this list. An empty list corresponds to the default algorithms that are `ES256` and `RS256` (in this order). Those two algorithms are required by the specification. -{% code lineNumbers="true" %} -```php +```php linenums="1" use Cose\Algorithms; use Webauthn\PublicKeyCredentialParameters; @@ -119,13 +114,12 @@ $publicKeyCredentialParametersList = [ PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED256), // Less interesting algorithm ]; ``` -{% endcode %} -{% hint style="warning" %} -Customizing this list may lead to unexpected behavior. Please use with caution. -{% endhint %} +!!! warning -### Authenticator Selection + Customizing this list may lead to unexpected behavior. Please use with caution. + +### Authenticator selection Please read detail [on this page](advanced-behaviours/authenticator-selection-criteria.md). @@ -135,9 +129,9 @@ Please read detail [on this page](advanced-behaviours/attestation-and-metadata-s ### Hints -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} +!!! info + + **New in v5.3.0** WebAuthn Level 3 lets the relying party suggest one or several authentication methods through the `hints` parameter. The user agent uses the list (in order) to tailor the prompt — for example, showing the QR code for hybrid transport first. @@ -147,8 +141,7 @@ The valid values are exposed as constants on `Webauthn\PublicKeyCredentialOption * `HINT_CLIENT_DEVICE` (`client-device`) — platform authenticator (Touch ID, Windows Hello…) * `HINT_HYBRID` (`hybrid`) — phone as authenticator over the hybrid transport -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialOptions; @@ -164,16 +157,14 @@ $publicKeyCredentialCreationOptions = ) ; ``` -{% endcode %} The same `hints` parameter is available on `PublicKeyCredentialRequestOptions` for authentication ceremonies. -### Exclude Credentials +### Exclude credentials When the user already registered authenticators, you can pass a list of `Webauthn\PublicKeyCredentialDescriptor` objects as argument to avoid registering multiple times the same authenticator. -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialDescriptor; @@ -192,20 +183,18 @@ $publicKeyCredentialCreationOptions = ) ; ``` -{% endcode %} -{% hint style="danger" %} -To avoid username enumeration, you should not set any excluded credential when the authenticator registration is performed for anonymous users. If this value is present, it indicates that the username exists and has registered credentials. +!!! danger -On the contrary when adding new authenticators to already authenticated users, this value should be set. -{% endhint %} + To avoid username enumeration, you should not set any excluded credential when the authenticator registration is performed for anonymous users. If this value is present, it indicates that the username exists and has registered credentials. -## Creation Response + On the contrary when adding new authenticators to already authenticated users, this value should be set. + +## Creation response What you receive must be a JSON object that looks like the following: -{% code lineNumbers="true" %} -```javascript +```javascript linenums="1" { "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA", "type":"public-key", @@ -216,19 +205,19 @@ What you receive must be a JSON object that looks like the following: } } ``` -{% endcode %} There are two steps to perform with this object: * [Load the data](input-loading.md) * [Verify it with the creation options set above](input-validation.md) -### Data Loading +### Data loading + +Now that all components are set, we can load the data we receive using the [_Serializer_](input-loading.md#the-serializer) (variable `$serializer`). -Now that all components are set, we can load the data we receive using the [_Serializer_](webauthn-server.md#the-serializer) (variable `$serializer`). +```php linenums="1" hl_lines="1" +<?php - declare(strict_types=1); use Webauthn\PublicKeyCredential; @@ -249,11 +238,11 @@ $publicKeyCredential = $serializer->deserialize( PublicKeyCredential::class, 'json' ); - +``` If no exception is thrown, you can go to the next step: the verification. -### Response Verification +### Response verification Now we have a fully loaded Public Key Credential object, but we need now to make sure that: @@ -262,8 +251,7 @@ Now we have a fully loaded Public Key Credential object, but we need now to make The first step is easy to perform: -{% code lineNumbers="true" %} -```php +```php linenums="1" response instanceof AuthenticatorAttestationResponse) //e.g. process here with a redirection to the public key creation page. } ``` -{% endcode %} The second step is the verification against @@ -283,8 +270,7 @@ The second step is the verification against The Authenticator Attestation Response Validator service (variable `$authenticatorAttestationResponseValidator`) will check everything for you: challenge, origin, attestation statement and much more. -{% code lineNumbers="true" %} -```php +```php linenums="1" check( 'my-application.com' ); ``` -{% endcode %} If no exception is thrown, the response is valid. You can store the Credential Record (`$credentialRecord`). -{% hint style="info" %} -The way you store and associate these objects to the user is out of scope of this library. -{% endhint %} +!!! info + + The way you store and associate these objects to the user is out of scope of this library. diff --git a/pure-php/input-loading.md b/docs/pure-php/input-loading.md similarity index 79% rename from pure-php/input-loading.md rename to docs/pure-php/input-loading.md index cedbaa5..72282c1 100644 --- a/pure-php/input-loading.md +++ b/docs/pure-php/input-loading.md @@ -1,14 +1,14 @@ -# Input Loading +# Input loading In general, the data you receive is an encoded JSON object. The library provides utilities to convert the string into objects. To do so, you will need an Attestation Statement Support Manager and a Serializer. -## Attestation Statement Support Manager +## Attestation statement support manager Authenticator Responses may contain an Attestation Statement. This attestation holds data regarding the authenticator depending on several factors such as its manufacturer and model, what you asked in the options, the capabilities of the browser or what the user allowed. -### Supported Attestation Statement Types +### Supported attestation statement types The following attestation types are supported. Note that you should only use the `none` one unless you have specific needs described in [the dedicated page](../webauthn-in-a-nutshell/attestation-and-metadata-statement.md). @@ -19,8 +19,7 @@ The following attestation types are supported. Note that you should only use the * `trusted platform module`: for devices with built-in security chips. * `apple`: for Apple devices -{% code lineNumbers="true" %} -```php +```php linenums="1" add(NoneAttestationStatementSupport::create()); ``` -{% endcode %} -## The Serializer +## The serializer To convert Authenticator Responses from the encoded string to an object, you will need a Serializer. It only needs the Attestation Statement Support Manager created above. -{% code lineNumbers="true" %} -```php +```php linenums="1" create(); ``` -{% endcode %} -{% hint style="danger" %} -Please do not use `json_encode` or `json_decode` to convert this data either way. -{% endhint %} +!!! danger -## Loading Data + Please do not use `json_encode` or `json_decode` to convert this data either way. + +## Loading data In general, the data you receive looks like as follows. -{% code lineNumbers="true" %} -```json +```json linenums="1" { "id":"KVb8CnwDjpgAo[…]op61BTLaa0tczXvz4JrQ23usxVHA8QJZi3L9GZLsAtkcVvWObA", "type":"public-key", @@ -71,14 +66,14 @@ In general, the data you receive looks like as follows. } } ``` -{% endcode %} -{% hint style="info" %} -Only this type of input is supported. If you receive other forms of data, please contact us. -{% endhint %} +!!! info + + Only this type of input is supported. If you receive other forms of data, please contact us. + +```php linenums="1" hl_lines="1" +<?php - declare(strict_types=1); use Webauthn\PublicKeyCredential; @@ -89,10 +84,10 @@ $publicKeyCredential = $serializer->deserialize( PublicKeyCredential::class, 'json' ); - +``` If the data is correctly loaded, the variable `$publicKeyCredential` will be an instance of `Webauthn\PublicKeyCredential`. An exception is thrown in case of an error. -{% hint style="danger" %} -At this stage, the data is not verified. -{% endhint %} +!!! danger + + At this stage, the data is not verified. diff --git a/pure-php/input-validation.md b/docs/pure-php/input-validation.md similarity index 51% rename from pure-php/input-validation.md rename to docs/pure-php/input-validation.md index e538a43..ee2d734 100644 --- a/pure-php/input-validation.md +++ b/docs/pure-php/input-validation.md @@ -1,17 +1,16 @@ -# Input Validation +# Input validation The loaded data needs to be verified. The library will perform several actions to make sure the input you received is valid. This verification process is performed by a Ceremony Step Manager (CSM). The WebAuthn specification distinguishes two types of ceremonies [described in this page](../webauthn-in-a-nutshell/ceremonies.md). -{% hint style="info" %} -**Changed in v5.3.6:** the `tokenBinding` member of the client data is not validated anymore. It is reserved since WebAuthn Level 3 and its value was never used, so a ceremony is no longer rejected because of it. The raw client data stays available through the `data` property of `CollectedClientData`. -{% endhint %} +!!! info -## Ceremony Step Manager Factory + **Changed in v5.3.6:** the `tokenBinding` member of the client data is not validated anymore. It is reserved since WebAuthn Level 3 and its value was never used, so a ceremony is no longer rejected because of it. The raw client data stays available through the `data` property of `CollectedClientData`. + +## Ceremony step manager factory To facilitate the creation of the CSM, a default factory is included. This factory requires no external services to function. -{% code lineNumbers="true" %} -```php +```php linenums="1" creationCeremony(); $requestCSM = $csmFactory->requestCeremony(); ``` -{% endcode %} -{% hint style="info" %} -You can customize its behavior to fit the specific needs of your application by modifying the provided factory. Please refer to the dedicated pages for more information. +!!! info + + You can customize its behavior to fit the specific needs of your application by modifying the provided factory. Please refer to the dedicated pages for more information. -* [Counter Checker](advanced-behaviours/authenticator-counter.md) -* [Attestation Statement Support Manager](advanced-behaviours/attestation-and-metadata-statement.md#attestation-statement-support-manager) -* [Extension Output Checker Handler](advanced-behaviours/extensions.md) -* [Algorithm Manager](advanced-behaviours/authenticator-algorithms.md) -* [Metadata Statement Repository](advanced-behaviours/attestation-and-metadata-statement.md#metadata-statement-repository) -* [Status Report Repository](advanced-behaviours/attestation-and-metadata-statement.md#status-report-repository) -* [Certification Chain Validator](advanced-behaviours/attestation-and-metadata-statement.md#certificate-chain-validator) -{% endhint %} + * [Counter Checker](advanced-behaviours/authenticator-counter.md) + * [Attestation Statement Support Manager](advanced-behaviours/attestation-and-metadata-statement.md#attestation-statement-support-manager) + * [Extension Output Checker Handler](advanced-behaviours/extensions.md) + * [Algorithm Manager](advanced-behaviours/authenticator-algorithms.md) + * [Metadata Statement Repository](advanced-behaviours/attestation-and-metadata-statement.md#metadata-statement-repository) + * [Status Report Repository](advanced-behaviours/attestation-and-metadata-statement.md#status-report-repository) + * [Certification Chain Validator](advanced-behaviours/attestation-and-metadata-statement.md#certificate-chain-validator) These CSM services are meant to be used by Response Validators. In a similar way, there are two types of validators: * **Authenticator Attestation Response Validator**: used during the creation ceremony * **Authenticator Assertion Response Validator**: used during the request ceremony -## Response Validators +## Response validators The Authenticator Attestation Response Validator and Authenticator Assertion Response Validator services are directly used when receiving Authenticator Responses in order to [register authenticators](authenticator-registration.md) or [authenticate users](authenticate-your-users.md). -{% hint style="info" %} -All null values below correspond to deprecated parameters. They have been removed in 5.0.0 -{% endhint %} +!!! info + + All null values below correspond to deprecated parameters. They have been removed in 5.0.0 -{% code lineNumbers="true" %} -```php +```php linenums="1" check( // 5. Store the credential $credentialRepository->saveCredentialSource($credentialSource); ``` -{% endcode %} -### Authentication Flow +### Authentication flow -{% code lineNumbers="true" %} -```php +```php linenums="1" check( // 5. Update credential (counter may have changed) $credentialRepository->saveCredentialSource($updatedCredential); ``` -{% endcode %} -## Minimal Setup Example +## Minimal setup example Here's a minimal working example with in-memory storage: -{% code lineNumbers="true" %} -```php +```php linenums="1" `, are compared verbatim with the origin sent by the client. Entries made of a host only are normalized to `https://`. -{% endhint %} +!!! info -## Top Origin Validation (Cross-Origin iframes) + **Changed in v5.3.3:** entries that are not URLs, such as `android:apk-key-hash:`, are compared verbatim with the origin sent by the client. Entries made of a host only are normalized to `https://`. + +## Top origin validation (cross-origin iframes) When the ceremony runs in a cross-origin iframe, the client data contains the origin of the embedding page in a `topOrigin` field. -{% hint style="danger" %} -**Changed in v5.3.4:** such a response is rejected unless a top origin validator is configured. Applications that do not use iframes are not affected, as a same-origin ceremony carries no `topOrigin`. -{% endhint %} +!!! danger + + **Changed in v5.3.4:** such a response is rejected unless a top origin validator is configured. Applications that do not use iframes are not affected, as a same-origin ceremony carries no `topOrigin`. Write a service implementing `Webauthn\CeremonyStep\TopOriginValidator`: -{% code title="src/Security/MyTopOriginValidator.php" lineNumbers="true" %} -```php +```php title="src/Security/MyTopOriginValidator.php" linenums="1" console.log('Registration failure')) ; ``` -{% endcode %} -### Creation Profile +### Creation profile The `default` [creation profile](../configuration-references.md#creation-profiles) is used. You can change it using the dedicated option. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -137,9 +127,8 @@ webauthn: … profile: custom_profile ``` -{% endcode %} -### Response Handlers +### Response handlers You can customize the responses returned by the controllers by using custom handlers. This could be useful when you want to return additional information to your application. @@ -149,12 +138,11 @@ There are 3 types of responses and handlers: * Success, * Failure. -#### Creation Options Handler +#### Creation options handler This handler is called during the registration of an authenticator and has to implement the interface `Webauthn\Bundle\Security\Handler\CreationOptionsHandler`. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -163,14 +151,12 @@ webauthn: … options_handler: … # Your handler here ``` -{% endcode %} -#### Success Handler +#### Success handler This handler is called when a client sends a valid assertion from the authenticator. This handler shall implement the interface `Webauthn\Bundle\Security\Handler\SuccessHandler`. The default handler is `Webauthn\Bundle\Service\DefaultSuccessHandler`. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -179,14 +165,12 @@ webauthn: … success_handler: … # Your handler here ``` -{% endcode %} -#### Failure Handler +#### Failure handler This handler is called when an error occurred during the process. This handler shall implement the interface `Webauthn\Bundle\Security\Handler\FailureHandler`. The default handler is `Webauthn\Bundle\Service\DefaultFailureHandler`. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -195,4 +179,3 @@ webauthn: … failure_handler: … # Your handler here ``` -{% endcode %} diff --git a/symfony-bundle/advanced-behaviors/user-verification.md b/docs/symfony-bundle/advanced-behaviors/user-verification.md similarity index 85% rename from symfony-bundle/advanced-behaviors/user-verification.md rename to docs/symfony-bundle/advanced-behaviors/user-verification.md index a6ae284..00bae3f 100644 --- a/symfony-bundle/advanced-behaviors/user-verification.md +++ b/docs/symfony-bundle/advanced-behaviors/user-verification.md @@ -1,9 +1,8 @@ -# User Verification +# User verification The easiest way to manage the user verification requirements in your Symfony application is by using the creation and request profiles. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: … creation_profiles: @@ -18,4 +17,3 @@ webauthn: rp_id: 'example.com' user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED ``` -{% endcode %} diff --git a/symfony-bundle/bundle-installation.md b/docs/symfony-bundle/bundle-installation.md similarity index 62% rename from symfony-bundle/bundle-installation.md rename to docs/symfony-bundle/bundle-installation.md index 07cd661..6f2edce 100644 --- a/symfony-bundle/bundle-installation.md +++ b/docs/symfony-bundle/bundle-installation.md @@ -1,12 +1,12 @@ -# Bundle Installation +# Bundle installation This framework provides a Symfony bundle that will help you to use the components within your Symfony application. -{% hint style="info" %} -Implementations for other frameworks and web applications exist. +!!! info -As an example, if you use Laravel, you may be interested in [this project: https://github.com/asbiin/laravel-webauthn](https://github.com/asbiin/laravel-webauthn) -{% endhint %} + Implementations for other frameworks and web applications exist. + + As an example, if you use Laravel, you may be interested in [this project: https://github.com/asbiin/laravel-webauthn](https://github.com/asbiin/laravel-webauthn) ## Installation @@ -16,16 +16,15 @@ As an example, if you use Laravel, you may be interested in [this project: https composer require web-auth/webauthn-symfony-bundle ``` -{% hint style="info" %} -As the recipes are third party ones not officially supported by Symfony, you will be asked to execute the recipe or not. When applied, the recipes will prompt a message such as `WebAuthn Framework is ready.` -{% endhint %} +!!! info + + As the recipes are third party ones not officially supported by Symfony, you will be asked to execute the recipe or not. When applied, the recipes will prompt a message such as `WebAuthn Framework is ready.` ### Without Symfony Flex If you don't use Symfony Flex, you must register the bundle and route manually. -{% code title="config/bundles.php" lineNumbers="true" %} -```php +```php title="config/bundles.php" linenums="1" ['all' => true], ]; ``` -{% endcode %} -{% code title="config/routes/webauthn.php" lineNumbers="true" %} -```php +```php title="config/routes/webauthn.php" linenums="1" import('.', 'webauthn'); }; ``` -{% endcode %} ## Repositories diff --git a/symfony-bundle/configuration-references.md b/docs/symfony-bundle/configuration-references.md similarity index 72% rename from symfony-bundle/configuration-references.md rename to docs/symfony-bundle/configuration-references.md index 11b72f3..4e2e78b 100644 --- a/symfony-bundle/configuration-references.md +++ b/docs/symfony-bundle/configuration-references.md @@ -1,4 +1,4 @@ -# Configuration References +# Configuration references ## Configuration @@ -8,8 +8,7 @@ You may also need to adjust other parameters. If you don't use Flex, hereafter an example of configuration file: -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: # logger: null # PSR-3 compatible logging service credential_repository: 'Webauthn\Bundle\Repository\DummyPublicKeyCredentialSourceRepository' # CREATE YOUR REPOSITORY AND CHANGE THIS! @@ -88,32 +87,30 @@ webauthn: # status_report_repository: 'App\Repository\StatusReportRepository' # certificate_chain_checker: 'App\Security\CertificateChainChecker' ``` -{% endcode %} -### Creation Profiles +### Creation profiles -{% hint style="success" %} -If you don't create the `creation_profiles` section, a `default` profile is set. -{% endhint %} +!!! success -#### Relying Party (rp) + If you don't create the `creation_profiles` section, a `default` profile is set. + +#### Relying party (rp) The Relying Party corresponds to your application. Please refer [to this page](../prerequisites/the-relying-party.md) for more information. -{% hint style="warning" %} -The parameter `id` is optional but highly recommended. -{% endhint %} +!!! warning + + The parameter `id` is optional but highly recommended. -{% hint style="info" %} -The parameters `name` and `icon` are deprecated (since 5.3.0 and 5.1.0 respectively) and are removed in 6.0.0. Since 5.3.7, a profile without `rp.name` produces options where the `name` member defaults to the Relying Party ID, and no deprecation is triggered. -{% endhint %} +!!! info -#### Challenge Length + The parameters `name` and `icon` are deprecated (since 5.3.0 and 5.1.0 respectively) and are removed in 6.0.0. Since 5.3.7, a profile without `rp.name` produces options where the `name` member defaults to the Relying Party ID, and no deprecation is triggered. + +#### Challenge length By default, the length of the challenge is 32 bytes. You may need to select a smaller or higher length. This length can be configured for each profile: -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -121,14 +118,12 @@ webauthn: name: 'ACME WebAuthn Server' challenge_length: 16 ``` -{% endcode %} #### Timeout The default timeout is set to 60 seconds (60 000 milliseconds). You can change this value as follows: -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -136,23 +131,21 @@ webauthn: name: 'ACME WebAuthn Server' timeout: 30000 ``` -{% endcode %} -{% hint style="info" %} -For v4.0+, the timeout is set to `null`. The values recommended by the specification are as follows: +!!! info + + For v4.0+, the timeout is set to `null`. The values recommended by the specification are as follows: -* If the user verification is `discouraged`, timeout should be between 30 and 180 seconds -* If the user verification is `preferred` or `required`, the range is 300 to 600 seconds (5 to 10 minutes) + * If the user verification is `discouraged`, timeout should be between 30 and 180 seconds + * If the user verification is `preferred` or `required`, the range is 300 to 600 seconds (5 to 10 minutes) -These behaviors are not necessarily followed by the web browsers. -{% endhint %} + These behaviors are not necessarily followed by the web browsers. -#### Authenticator Selection Criteria +#### Authenticator selection criteria This set of options allows you to select authenticators depending on their capabilities. The values are described in [the advanced concepts](../pure-php/advanced-behaviours/authenticator-selection-criteria.md) of the protocol. -{% code title="app/config/webauthn.yaml" lineNumbers="true" fullWidth="false" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -163,22 +156,20 @@ webauthn: require_resident_key: true user_verification: !php/const Webauthn\AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED ``` -{% endcode %} -#### Public Key Credential Parameters +#### Public key credential parameters This option indicates the algorithms allowed for your application. By default, a large list of algorithms is defined, but you can add custom algorithms or reduce the list. -{% hint style="info" %} -The order is important. Preferred algorithms go first. -{% endhint %} +!!! info -{% hint style="warning" %} -It is not recommended to change the default list unless you exactly know what you are doing. -{% endhint %} + The order is important. Preferred algorithms go first. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +!!! warning + + It is not recommended to change the default list unless you exactly know what you are doing. + +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -188,40 +179,36 @@ webauthn: - !php/const Cose\Algorithms::COSE_ALGORITHM_ES256 - !php/const Cose\Algorithms::COSE_ALGORITHM_RS256 ``` -{% endcode %} -#### Signature Verification Algorithms +#### Signature verification algorithms The parameters above list the algorithms offered to the authenticator during the registration. The signatures themselves are verified by `webauthn.cose.algorithm.manager`, which the bundle fills with one service per supported algorithm. Any service implementing `Cose\Algorithm\Algorithm` is collected automatically, so declaring one is enough to add an algorithm to that manager. -{% hint style="danger" %} -**Backward compatibility break in v5.3.8:** the bundle no longer registers `RS1` (RSASSA-PKCS1-v1\_5 with SHA-1). SHA-1 is no longer acceptable for digital signatures, and building the algorithm emits an `E_USER_WARNING` that the Symfony error handler turns into an exception, so every ceremony answered with a 500 in the dev environment. +!!! danger -`RS1` was never offered at the registration, as `public_key_credential_parameters` is empty by default. The applications that added `Cose\Algorithms::COSE_ALGORITHM_RS1` to that list are the only ones affected: without the service, the assertions signed by the credentials they registered are rejected. -{% endhint %} + **Backward compatibility break in v5.3.8:** the bundle no longer registers `RS1` (RSASSA-PKCS1-v1\_5 with SHA-1). SHA-1 is no longer acceptable for digital signatures, and building the algorithm emits an `E_USER_WARNING` that the Symfony error handler turns into an exception, so every ceremony answered with a 500 in the dev environment. + + `RS1` was never offered at the registration, as `public_key_credential_parameters` is empty by default. The applications that added `Cose\Algorithms::COSE_ALGORITHM_RS1` to that list are the only ones affected: without the service, the assertions signed by the credentials they registered are rejected. If you are in that situation, declare the algorithm yourself. Autoconfiguration adds it back to the manager. -{% code title="config/services.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/services.yaml" linenums="1" services: webauthn.cose.algorithm.RS1: class: Cose\Algorithm\Signature\RSA\RS1 arguments: - true # Acknowledge that the algorithm is insecure ``` -{% endcode %} -#### Attestation Conveyance +#### Attestation conveyance If you need the [attestation of the authenticator](../webauthn-in-a-nutshell/attestation-and-metadata-statement.md), you can specify the preference regarding attestation conveyance during credential generation. -{% hint style="warning" %} -The use of Attestation Statements is generally not recommended unless you REALLY need this information. -{% endhint %} +!!! warning + + The use of Attestation Statements is generally not recommended unless you REALLY need this information. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -229,18 +216,16 @@ webauthn: name: 'ACME WebAuthn Server' attestation_conveyance: !php/const Webauthn\PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT ``` -{% endcode %} #### Hints -{% hint style="info" %} -**New in v5.3.0:** Support for WebAuthn hints to guide user authentication choices. -{% endhint %} +!!! info + + **New in v5.3.0:** Support for WebAuthn hints to guide user authentication choices. Hints provide guidance to the user agent about how the user might authenticate. This can help improve the user experience by suggesting the most appropriate authentication method. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -257,23 +242,21 @@ webauthn: - 'security-key' - 'client-device' ``` -{% endcode %} Available hint values: - `security-key`: External security key (like YubiKey) - `client-device`: Platform authenticator (like Touch ID, Windows Hello) - `hybrid`: Hybrid transport (like phone as authenticator) -#### Hide Existing Credentials +#### Hide existing credentials + +!!! info -{% hint style="info" %} -**New in v5.3.0:** Exposed in bundle configuration. -{% endhint %} + **New in v5.3.0:** Exposed in bundle configuration. When set to `true`, the server will automatically add the user's existing credentials to the `excludeCredentials` list in the creation options. This prevents the user from re-registering the same authenticator. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -281,18 +264,16 @@ webauthn: name: 'ACME WebAuthn Server' hide_existing_credentials: true ``` -{% endcode %} -#### Conditional Create +#### Conditional create -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} +!!! info + + **New in v5.3.0** Enable Conditional Create (auto-register) for a profile. When `true`, user presence can be `false` during registration, allowing silent credential creation after the user has authenticated via another method (e.g., password). -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: passkey_upgrade: @@ -302,20 +283,18 @@ webauthn: authenticator_selection_criteria: require_resident_key: true ``` -{% endcode %} See [Conditional Create](../pure-php/advanced-behaviours/conditional-create.md) for detailed usage. -#### Client Override Policy +#### Client override policy + +!!! info -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} + **New in v5.3.0** Control which WebAuthn options can be overridden by client request parameters. See [Client Override Policy](advanced-behaviors/client-override-policy.md) for detailed configuration. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -328,18 +307,16 @@ webauthn: authenticator_attachment: enabled: false # Prevent client override ``` -{% endcode %} #### Extensions You can set as many extensions as you want in the profile. Please also [refer to this page](../webauthn-in-a-nutshell/extensions.md) for more information. -{% hint style="info" %} -The example below is totally fictive. Some extensions are [defined in the specification](https://www.w3.org/TR/webauthn/#sctn-defined-extensions) but the support depends on the authenticators, on the browsers and on the relying parties (your applications). -{% endhint %} +!!! info -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml + The example below is totally fictive. Some extensions are [defined in the specification](https://www.w3.org/TR/webauthn/#sctn-defined-extensions) but the support depends on the authenticators, on the browsers and on the relying parties (your applications). + +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: creation_profiles: acme: @@ -349,49 +326,42 @@ webauthn: loc: true txAuthSimple: 'Please add your new authenticator' ``` -{% endcode %} -### Request Profiles +### Request profiles + +!!! success -{% hint style="success" %} -If you don't create the `request_profiles` section, a `default` profile is set. -{% endhint %} + If you don't create the `request_profiles` section, a `default` profile is set. The parameters for the request profiles (i.e. the authentication) are very similar to the creation profiles. The only difference is that you don't need all the detail of the Relying Party, but only its ID (i.e. its domain). -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: request_profiles: acme: rp_id: 'example.com' ``` -{% endcode %} Please note that all parameters are optional. The following configuration is perfectly valid. However, and as mentioned above, the parameter `id` is highly recommended. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: request_profiles: acme: ~ ``` -{% endcode %} -### Passkey Endpoints +### Passkey endpoints + +!!! info -{% hint style="info" %} -**New in v5.3.0** -{% endhint %} + **New in v5.3.0** Enable the `.well-known/passkey-endpoints` discovery endpoint. See [Passkey Endpoints](advanced-behaviors/passkey-endpoints.md) for detailed configuration. -{% code title="app/config/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="app/config/webauthn.yaml" linenums="1" webauthn: passkey_endpoints: enabled: true enroll: 'https://example.com/passkeys/register' manage: 'https://example.com/passkeys/manage' ``` -{% endcode %} diff --git a/symfony-bundle/credential-record-repository.md b/docs/symfony-bundle/credential-record-repository.md similarity index 56% rename from symfony-bundle/credential-record-repository.md rename to docs/symfony-bundle/credential-record-repository.md index 0ef5293..a9df6fb 100644 --- a/symfony-bundle/credential-record-repository.md +++ b/docs/symfony-bundle/credential-record-repository.md @@ -2,50 +2,49 @@ description: Where the public keys and details are stored --- -# Credential Record Repository +# Credential record repository The Credential Record can be stored the way you want. As the `Webauthn\CredentialRecord` class can be converted into JSON, it could be stored in a filesystem. It is up to you to create a credential record repository. This service shall implement `Webauthn\Bundle\Repository\CredentialRecordRepositoryInterface`. -{% hint style="warning" %} -**Renamed in v5.3.0:** `PublicKeyCredentialSource` has been renamed to `CredentialRecord` and `PublicKeyCredentialSourceRepositoryInterface` to `CredentialRecordRepositoryInterface`. The old names are deprecated and will be removed in 6.0. -{% endhint %} +!!! warning -{% hint style="info" %} -**Fixed in v5.3.7:** the bundle aliases the deprecated `PublicKeyCredentialSourceRepositoryInterface` to your repository only when that repository actually implements it. Symfony refuses an interface alias whose target does not implement the interface, so a repository following the deprecation and implementing `CredentialRecordRepositoryInterface` alone made the container invalid. Applications whose repository still implements the legacy interface keep the alias, and code type-hinting it keeps working. -{% endhint %} + **Renamed in v5.3.0:** `PublicKeyCredentialSource` has been renamed to `CredentialRecord` and `PublicKeyCredentialSourceRepositoryInterface` to `CredentialRecordRepositoryInterface`. The old names are deprecated and will be removed in 6.0. -{% hint style="warning" %} -Doctrine users: the field type for `transports` and `other_ui` changed from `array` to `json` (`array` is now deprecated) between the bundle v4.x and 5.0. +!!! info -Please make sure to reflect the changes to your data model. + **Fixed in v5.3.7:** the bundle aliases the deprecated `PublicKeyCredentialSourceRepositoryInterface` to your repository only when that repository actually implements it. Symfony refuses an interface alias whose target does not implement the interface, so a repository following the deprecation and implementing `CredentialRecordRepositoryInterface` alone made the container invalid. Applications whose repository still implements the legacy interface keep the alias, and code type-hinting it keeps working. -Here is an example for Postgres: +!!! warning -```sql -ALTER TABLE [/*TABLE NAME HERE*/] ALTER transports TYPE JSON USING transports::JSON -ALTER TABLE [/*TABLE NAME HERE*/] ALTER other_ui TYPE JSON USING other_ui::JSON + Doctrine users: the field type for `transports` and `other_ui` changed from `array` to `json` (`array` is now deprecated) between the bundle v4.x and 5.0. -``` -{% endhint %} + Please make sure to reflect the changes to your data model. + + Here is an example for Postgres: + + ```sql + ALTER TABLE [/*TABLE NAME HERE*/] ALTER transports TYPE JSON USING transports::JSON + ALTER TABLE [/*TABLE NAME HERE*/] ALTER other_ui TYPE JSON USING other_ui::JSON -## Registration Capability + ``` + +## Registration capability By default, the User Entity Repository is not able to register any user account. You can add this behavior by implementing the interface `Webauthn\Bundle\Repository\CanRegisterUserEntity`. -## Doctrine Repository +## Doctrine repository In general, Symfony applications use Doctrine, which is why the bundle provides a way to use Doctrine as storage system. -### The Doctrine Entity +### The Doctrine entity Here is an example of an entity. This is the most simple example. Feel free to add custom fields that fit your needs e.g. `created_at` or `is_revoked`. -{% code title="App/Entity/WebauthnCredential.php" lineNumbers="true" %} -```php +```php title="App/Entity/WebauthnCredential.php" linenums="1" <?php +!!! warning + + We must override the method `saveCredentialRecord` because we may receive `Webauthn\CredentialRecord` objects instead of `App\Entity\WebauthnCredential`. + +```php title="App/Repository/WebauthnCredentialRepository.php" linenums="1" hl_lines="7" +use App\Entity\WebauthnCredential; -use Doctrine\Persistence\ManagerRegistry; +use App\Entity\WebauthnCredential; +use Doctrine\Persistence\ManagerRegistry; use Webauthn\Bundle\Repository\DoctrineCredentialSourceRepository; use Webauthn\CredentialRecord; @@ -152,18 +151,15 @@ final class WebauthnCredentialRepository extends DoctrineCredentialSourceReposit parent::saveCredentialSource($credentialRecord); } } - - +``` This repository should be declared as a Symfony service. -{% hint style="info" %} -With Symfony autowiring and autoconfiguration, this is usually done automatically -{% endhint %} +!!! info + + With Symfony autowiring and autoconfiguration, this is usually done automatically -{% code title="config/services.yaml" %} -```yaml +```yaml title="config/services.yaml" services: App\Repository\WebauthnCredentialRepository: ~ ``` -{% endcode %} diff --git a/symfony-bundle/firewall.md b/docs/symfony-bundle/firewall.md similarity index 89% rename from symfony-bundle/firewall.md rename to docs/symfony-bundle/firewall.md index 3e72b7c..2825d88 100644 --- a/symfony-bundle/firewall.md +++ b/docs/symfony-bundle/firewall.md @@ -4,14 +4,13 @@ description: How to register and authenticate my users? # Firewall -## Security Bundle +## Security bundle To authenticate or register your users with Symfony, the best and easiest way is to use the Security Bundle. First, install that bundle and follow the instructions given by [the official documentation](https://symfony.com/doc/current/security.html). At the end of the installation and configuration, you should have a `config/packages/security.yaml` file that looks like the following: -{% code title="config/packages/security.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/security.yaml" linenums="1" security: providers: default: @@ -22,7 +21,6 @@ security: path: 'logout' ... ``` -{% endcode %} ## Controller @@ -88,7 +86,7 @@ Below is an example of a login form using the [Stimulus Controller](../symfony-u {% endblock %} ``` -## Login Authenticator +## Login authenticator Next, we need a Symfony Authenticator to handle login form submissions. With WebAuthn, we use a dedicated Passport that shall contain a specific Badge. @@ -145,12 +143,11 @@ final class LoginAuthenticator extends WebauthnAuthenticator } ``` -## Security Configuration +## Security configuration To enable the user authentication, you just have to declare the Symfony Authenticator in the appropriate firewall (here `main`). -{% code title="config/packages/security.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/security.yaml" linenums="1" security: providers: default: @@ -164,16 +161,14 @@ security: - { path: ^/login, roles: PUBLIC_ACCESS, requires_channel: 'https'} - { path: ^/logout, roles: PUBLIC_ACCESS} ``` -{% endcode %} -{% hint style="info" %} -**Multiple User Providers (v5.2.1+):** The bundle now supports multiple user providers. If your application uses multiple providers, you can configure them in the security configuration and the WebauthnBadge will work correctly with all of them. -{% endhint %} +!!! info + + **Multiple User Providers (v5.2.1+):** The bundle now supports multiple user providers. If your application uses multiple providers, you can configure them in the security configuration and the WebauthnBadge will work correctly with all of them. Also, you need to define the credential options endpoint. -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -181,13 +176,12 @@ webauthn: login: options_path: '/login/webauthn/options' ``` -{% endcode %} -## User Registration +## User registration TO BE WRITTEN -## Authentication Attributes +## Authentication attributes The security token returned by the firewall sets some attributes depending on the assertion and the capabilities of the authenticator. The attributes are: @@ -196,10 +190,8 @@ The security token returned by the firewall sets some attributes depending on th You can then set constraints to the access controls. In the example below, the /admin path can be reached by users with the role `ROLE_ADMIN` and that **have been verified** during the ceremony. -{% code title="config/packages/security.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/security.yaml" linenums="1" security: access_control: - { path: ^/admin, roles: [ROLE_ADMIN, IS_USER_VERIFIED]} ``` -{% endcode %} diff --git a/symfony-bundle/user-entity-repository.md b/docs/symfony-bundle/user-entity-repository.md similarity index 92% rename from symfony-bundle/user-entity-repository.md rename to docs/symfony-bundle/user-entity-repository.md index 57cdd1f..d14243b 100644 --- a/symfony-bundle/user-entity-repository.md +++ b/docs/symfony-bundle/user-entity-repository.md @@ -1,6 +1,6 @@ -# User Entity Repository +# User entity repository -## User Entity +## User entity Your application certainly has a user repository. Good news: there is no need to modify it! @@ -10,8 +10,7 @@ This repository should be declared as a Symfony service and shall implement `Web Here is an example where the application User Repository is injected. This repository uses Doctrine and provides `findOneBy*` methods. -{% code title="src/Repository/WebauthnUserEntityRepository.php" lineNumbers="true" %} -```php +```php title="src/Repository/WebauthnUserEntityRepository.php" linenums="1" {% endblock %} ``` -{% endcode %} -## Adding Additional Authenticators +## Adding additional authenticators Allow authenticated users to register additional authenticators: -{% code title="templates/security/add_authenticator.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/security/add_authenticator.html.twig" linenums="1" {% extends 'base.html.twig' %} {% block body %} @@ -135,18 +130,16 @@ Allow authenticated users to register additional authenticators: {% endblock %} ``` -{% endcode %} -{% hint style="info" %} -The controller configuration for `add_device` should use `CurrentUserEntityGuesser` to automatically get the authenticated user. See the [User Registration](user-registration.md#configuration-for-existing-users) page for configuration details. -{% endhint %} +!!! info -## Removing Authenticators + The controller configuration for `add_device` should use `CurrentUserEntityGuesser` to automatically get the authenticated user. See the [User Registration](user-registration.md#configuration-for-existing-users) page for configuration details. + +## Removing authenticators Allow users to remove authenticators they no longer use: -{% code title="src/Controller/SecurityController.php" lineNumbers="true" %} -```php +```php title="src/Controller/SecurityController.php" linenums="1" #[Route('/security/authenticators/{id}/remove', name: 'app_remove_authenticator')] #[IsGranted('ROLE_USER')] public function removeAuthenticator( @@ -177,23 +170,21 @@ public function removeAuthenticator( return $this->redirectToRoute('app_list_authenticators'); } ``` -{% endcode %} -{% hint style="warning" %} -**Important Security Considerations:** +!!! warning + + **Important Security Considerations:** -* Always verify the authenticator belongs to the current user before removal -* Prevent users from removing their last authenticator (would lock them out) -* Consider requiring re-authentication before removal for sensitive accounts -* Log authenticator additions and removals for security auditing -{% endhint %} + * Always verify the authenticator belongs to the current user before removal + * Prevent users from removing their last authenticator (would lock them out) + * Consider requiring re-authentication before removal for sensitive accounts + * Log authenticator additions and removals for security auditing -## Naming Authenticators +## Naming authenticators Allow users to give friendly names to their authenticators for easier management: -{% code title="src/Entity/WebauthnCredential.php" lineNumbers="true" %} -```php +```php title="src/Entity/WebauthnCredential.php" linenums="1" use Doctrine\ORM\Mapping as ORM; use Webauthn\CredentialRecord; @@ -231,12 +222,10 @@ class WebauthnCredential extends CredentialRecord // ... other methods } ``` -{% endcode %} Then allow users to set names during or after registration: -{% code title="templates/security/add_authenticator.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/security/add_authenticator.html.twig" linenums="1"
Register New Authenticator
``` -{% endcode %} -## Best Practices +## Best practices -### Encourage Backup Authenticators +### Encourage backup authenticators Prompt users to register a backup authenticator after their first registration: -{% code lineNumbers="true" %} -```twig +```twig linenums="1" {% if credential_count == 1 %}
Add a backup authenticator! @@ -270,9 +257,8 @@ Prompt users to register a backup authenticator after their first registration:
{% endif %} ``` -{% endcode %} -### Authenticator Metadata +### Authenticator metadata Display useful information about each authenticator: @@ -281,14 +267,14 @@ Display useful information about each authenticator: * **Registration date**: Track when each authenticator was added * **AAGUID**: Identify the authenticator model (if available) -### Security Recommendations +### Security recommendations * **Minimum authenticators**: Consider requiring at least 2 authenticators for privileged accounts * **Maximum authenticators**: Limit to prevent abuse (e.g., 10 per user) * **Inactive authenticators**: Automatically remove authenticators not used for a long period * **Notification**: Email users when authenticators are added or removed -## See Also +## See also * [User Registration](user-registration.md) - Register the first authenticator * [User Authentication](user-authentication.md) - Authenticate with any registered authenticator diff --git a/symfony-ux/installation.md b/docs/symfony-ux/installation.md similarity index 80% rename from symfony-ux/installation.md rename to docs/symfony-ux/installation.md index 184265b..43a42e8 100644 --- a/symfony-ux/installation.md +++ b/docs/symfony-ux/installation.md @@ -13,9 +13,9 @@ Before installing the Stimulus Controller, you need: Since v5.3.0 the Stimulus controllers are published on npm as [`@web-auth/webauthn-stimulus`](https://www.npmjs.com/package/@web-auth/webauthn-stimulus). The npm package is the canonical and only maintained source going forward — the legacy `web-auth/webauthn-stimulus` Composer package is deprecated and will be removed in 6.0.0. -{% hint style="danger" %} -**Do not declare this package in `assets/controllers.json`.** That file is reserved for Symfony **PHP** UX packages. `Symfony\UX\StimulusBundle` resolves every entry against an installed Composer package, so adding `@web-auth/webauthn-stimulus` there throws `Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json` as soon as the Composer wrapper is gone (the very thing we want). Register the controllers from your JavaScript code instead — see below. -{% endhint %} +!!! danger + + **Do not declare this package in `assets/controllers.json`.** That file is reserved for Symfony **PHP** UX packages. `Symfony\UX\StimulusBundle` resolves every entry against an installed Composer package, so adding `@web-auth/webauthn-stimulus` there throws `Could not find package "web-auth/webauthn-stimulus" referred to from controllers.json` as soon as the Composer wrapper is gone (the very thing we want). Register the controllers from your JavaScript code instead — see below. ### 1. Pin the package in your importmap @@ -37,8 +37,7 @@ The package depends on `@hotwired/stimulus` and `@simplewebauthn/browser`, which Import the controllers from the package entry point and register them on your Stimulus `Application`. Use the same `--`-separated identifiers that `stimulus_controller('@web-auth/webauthn-stimulus/...')` produces, so the existing Twig helpers keep working unchanged: -{% code title="assets/bootstrap.js" lineNumbers="true" %} -```javascript +```javascript title="assets/bootstrap.js" linenums="1" import { Application } from '@hotwired/stimulus'; import { AuthenticationController, @@ -55,19 +54,18 @@ app.register('web-auth--webauthn-stimulus--registration', RegistrationController // Legacy combined controller — only register if you still rely on it app.register('web-auth--webauthn-stimulus', WebauthnController); ``` -{% endcode %} -{% hint style="info" %} -**Why these identifiers?** `stimulus_controller('@vendor/pkg/ctrl')` simply transforms the name to a flat string by stripping the leading `@` and turning `/` into `--`. So `stimulus_controller('@web-auth/webauthn-stimulus/authentication')` renders `data-controller="web-auth--webauthn-stimulus--authentication"` — the same identifier you registered in JS. Existing Twig templates that use the `@`-prefixed form keep working without changes. +!!! info + + **Why these identifiers?** `stimulus_controller('@vendor/pkg/ctrl')` simply transforms the name to a flat string by stripping the leading `@` and turning `/` into `--`. So `stimulus_controller('@web-auth/webauthn-stimulus/authentication')` renders `data-controller="web-auth--webauthn-stimulus--authentication"` — the same identifier you registered in JS. Existing Twig templates that use the `@`-prefixed form keep working without changes. -The exact location of your Stimulus bootstrap file depends on your project layout. With the default Symfony AssetMapper recipe it is `assets/bootstrap.js` or `assets/app.js`. Anywhere your `Application.start()` runs is fine. -{% endhint %} + The exact location of your Stimulus bootstrap file depends on your project layout. With the default Symfony AssetMapper recipe it is `assets/bootstrap.js` or `assets/app.js`. Anywhere your `Application.start()` runs is fine. ### Deprecated: install via Composer (`web-auth/webauthn-stimulus`) -{% hint style="danger" %} -This installation path is kept for backward compatibility only. New projects should use the npm package above. -{% endhint %} +!!! danger + + This installation path is kept for backward compatibility only. New projects should use the npm package above. ```bash composer require web-auth/webauthn-stimulus @@ -75,7 +73,7 @@ composer require web-auth/webauthn-stimulus This command installs the PHP wrapper, which used to register the Stimulus controllers via Symfony Flex and configure AssetMapper to import them from a vendored copy. As of v5.3.0 the same JavaScript ships on npm and the PHP wrapper no longer brings any added value — it is scheduled for removal in 6.0.0. -## What's Included +## What's included The package provides Stimulus controllers that handle: @@ -88,17 +86,17 @@ The package provides Stimulus controllers that handle: * **PRF extension** — built-in support for the Pseudo-Random Function extension * **TypeScript declarations** (v5.3.3+): the package ships `.d.ts` files alongside the controllers and declares them through the `types` entry of its `package.json`, so editors and `tsc` know the values, targets, actions and events of each controller without any extra `@types` package -{% hint style="info" %} -**Three controllers ship in the package:** +!!! info + + **Three controllers ship in the package:** -* `WebauthnController` — combined controller (legacy, still supported) -* `AuthenticationController` — dedicated authentication controller -* `RegistrationController` — dedicated registration controller + * `WebauthnController` — combined controller (legacy, still supported) + * `AuthenticationController` — dedicated authentication controller + * `RegistrationController` — dedicated registration controller -The dedicated controllers offer better separation of concerns, enhanced conditional UI support, and improved error handling. The combined controller remains available for backward compatibility. -{% endhint %} + The dedicated controllers offer better separation of concerns, enhanced conditional UI support, and improved error handling. The combined controller remains available for backward compatibility. -## Basic Usage +## Basic usage Once the controllers are registered, you can use them in your Twig templates with the standard Symfony UX helpers: @@ -108,10 +106,9 @@ Once the controllers are registered, you can use them in your Twig templates wit The first argument is the package-prefixed name — it will resolve to the `--`-separated identifier you registered in step 2. -### Registration Form Example +### Registration form example -{% code title="templates/registration/register.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/registration/register.html.twig" linenums="1"
``` -{% endcode %} -### Authentication Form Example +### Authentication form example -{% code title="templates/security/login.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/security/login.html.twig" linenums="1"
``` -{% endcode %} -## Controller Options +## Controller options The dedicated controllers accept the following options. See the [User Authentication](user-authentication.md) and [User Registration](user-registration.md) pages for the full list of values, targets, actions and events. -### Registration Options +### Registration options | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -192,7 +186,7 @@ The dedicated controllers accept the following options. See the [User Authentica | `successRedirectUri` | string | – | URL to redirect after successful registration | | `autoRegister` | boolean | `false` | Auto-start registration on page load | -### Authentication Options +### Authentication options | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -225,7 +219,7 @@ If you see console errors: 3. Verify WebAuthn routes are properly configured in `config/routes/webauthn.yaml`. 4. Ensure the controller is loaded by checking the browser's network tab. -## HTTPS Requirement +## HTTPS requirement WebAuthn **requires HTTPS** to function. For development, use the Symfony CLI for automatic HTTPS: @@ -236,7 +230,7 @@ symfony serve For production, ensure HTTPS is properly configured on your web server. -## Next Steps +## Next steps Now that the Stimulus Controller is installed, proceed to: @@ -244,7 +238,7 @@ Now that the Stimulus Controller is installed, proceed to: 2. **[User Authentication](user-authentication.md)** — implement login flows 3. **[Additional Authenticators](additional-authenticators.md)** — allow users to register backup devices -## See Also +## See also * [Symfony UX Documentation](https://symfony.com/doc/current/frontend/ux.html) — official UX guide * [Stimulus Documentation](https://stimulus.hotwired.dev/) — Stimulus framework reference diff --git a/symfony-ux/user-authentication.md b/docs/symfony-ux/user-authentication.md similarity index 90% rename from symfony-ux/user-authentication.md rename to docs/symfony-ux/user-authentication.md index 624f30c..5654909 100644 --- a/symfony-ux/user-authentication.md +++ b/docs/symfony-ux/user-authentication.md @@ -1,4 +1,4 @@ -# User Authentication +# User authentication With the version 5.2 of the bundle, the login process is very similar to the username/password login. @@ -54,7 +54,7 @@ The `requestResultField` parameter corresponds to the selector to the hidden fie The behavior after the login is managed by your Symfony Security Authenticator. -### Browser Autofill +### Browser autofill When authenticators are available on the device and the browser is aware of them, you can simplify the way users will sign in. When this feature is enabled, the user will see the list of available authenticators when focusing on the username field. Selecting an account in the list will automatically perform the authentication actions. There is a simple option to enable this feature: @@ -68,18 +68,17 @@ When authenticators are available on the device and the browser is aware of them ) }} ``` -## Dedicated Authentication Controller +## Dedicated authentication controller -{% hint style="info" %} -**New in v5.3.0:** The Stimulus package now provides dedicated controllers for authentication and registration, in addition to the combined controller. -{% endhint %} +!!! info + + **New in v5.3.0:** The Stimulus package now provides dedicated controllers for authentication and registration, in addition to the combined controller. The new `authentication-controller` provides a focused controller specifically for sign-in flows, with built-in support for conditional UI and better error handling. -### Basic Usage +### Basic usage -{% code title="templates/security/login.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/security/login.html.twig" linenums="1"
``` -{% endcode %} -### Conditional UI (Autofill) +### Conditional UI (autofill) The dedicated controller supports conditional UI natively: -{% code lineNumbers="true" %} -```twig +```twig linenums="1" {{ stimulus_controller('@web-auth/webauthn-stimulus/authentication', { optionsUrl: path('webauthn.controller.request.request.login'), resultUrl: path('app_login'), @@ -124,9 +121,8 @@ The dedicated controller supports conditional UI natively: conditionalUi: true }) }} ``` -{% endcode %} -### Authentication Controller Options +### Authentication controller options | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -137,7 +133,7 @@ The dedicated controller supports conditional UI natively: | `conditionalUi` | boolean | `false` | Enable conditional UI (autofill) | | `verifyAutofillInput` | boolean | `true` | Verify autofill input fields exist | -### Authentication Controller Targets +### Authentication controller targets | Target | Description | |--------|-------------| diff --git a/symfony-ux/user-registration.md b/docs/symfony-ux/user-registration.md similarity index 79% rename from symfony-ux/user-registration.md rename to docs/symfony-ux/user-registration.md index 3fa707e..cd9ba8a 100644 --- a/symfony-ux/user-registration.md +++ b/docs/symfony-ux/user-registration.md @@ -1,20 +1,19 @@ -# User Registration +# User registration The Symfony UX WebAuthn package provides Stimulus controllers to simplify the registration of new authenticators. This page explains how to register a new user with WebAuthn or add authenticators to an existing user account. -## New User Registration +## New user registration When registering a new user, you need to collect basic information (username, display name) and then register their first authenticator. -### Registration Form +### Registration form Your registration form needs: 1. A **username field** for the user's identifier 2. A **display name field** (optional, can default to username) 3. A **hidden field** for the attestation response -{% code title="templates/registration/register.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/registration/register.html.twig" linenums="1"
``` -{% endcode %} -### Bundle Configuration +### Bundle configuration Configure the bundle to handle new user registration: -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: credential_repository: 'App\Repository\WebauthnCredentialRepository' user_repository: 'App\Repository\UserRepository' @@ -72,14 +69,12 @@ webauthn: result_path: '/register/webauthn' user_entity_guesser: 'App\Guesser\NewUserEntityGuesser' ``` -{% endcode %} -### User Entity Guesser +### User entity guesser Create a guesser to handle new user registration: -{% code lineNumbers="true" %} -```php +```php linenums="1" ``` -{% endcode %} -### Configuration for Existing Users +### Configuration for existing users -{% code title="config/packages/webauthn.yaml" lineNumbers="true" %} -```yaml +```yaml title="config/packages/webauthn.yaml" linenums="1" webauthn: controllers: enabled: true @@ -157,13 +148,12 @@ webauthn: result_path: '/devices/add' user_entity_guesser: 'Webauthn\Bundle\Security\Guesser\CurrentUserEntityGuesser' ``` -{% endcode %} -{% hint style="success" %} -The `CurrentUserEntityGuesser` automatically retrieves the currently authenticated user from the Symfony security context. No custom guesser needed! -{% endhint %} +!!! success + + The `CurrentUserEntityGuesser` automatically retrieves the currently authenticated user from the Symfony security context. No custom guesser needed! -## Stimulus Controller Options +## Stimulus controller options The registration controller accepts the following options: @@ -173,12 +163,11 @@ The registration controller accepts the following options: | `creationResultField` | string | Yes | CSS selector for the hidden attestation field | | `autoRegister` | boolean | No | Automatically trigger registration on page load (default: false) | -### Auto-Register Example +### Auto-register example Automatically start the registration process when the page loads: -{% code lineNumbers="true" %} -```twig +```twig linenums="1" {{ stimulus_controller('@web-auth/webauthn-stimulus', { autoRegister: true, @@ -187,24 +176,22 @@ Automatically start the registration process when the page loads: } ) }} ``` -{% endcode %} -{% hint style="warning" %} -Use `autoRegister` carefully as it immediately prompts the user for biometric authentication or security key interaction. -{% endhint %} +!!! warning + + Use `autoRegister` carefully as it immediately prompts the user for biometric authentication or security key interaction. + +## Dedicated registration controller -## Dedicated Registration Controller +!!! info -{% hint style="info" %} -**New in v5.3.0:** A dedicated registration controller is available alongside the combined controller. -{% endhint %} + **New in v5.3.0:** A dedicated registration controller is available alongside the combined controller. The dedicated `registration-controller` provides a focused controller for credential registration with enhanced features like conditional create support. -### Basic Usage +### Basic usage -{% code title="templates/registration/register.html.twig" lineNumbers="true" %} -```twig +```twig title="templates/registration/register.html.twig" linenums="1"
``` -{% endcode %} -### Registration Controller Options +### Registration controller options | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -246,7 +232,7 @@ The dedicated `registration-controller` provides a focused controller for creden | `successRedirectUri` | string | - | URL to redirect after successful registration | | `autoRegister` | boolean | `false` | Auto-start registration on page load | -### Registration Controller Targets +### Registration controller targets | Target | Description | |--------|-------------| @@ -257,12 +243,11 @@ The dedicated `registration-controller` provides a focused controller for creden | `authenticatorAttachment` | Select field for authenticator attachment preference | | `result` | Hidden input for the attestation response | -## Error Handling +## Error handling Handle registration errors in your custom event listeners: -{% code lineNumbers="true" %} -```javascript +```javascript linenums="1" document.addEventListener('webauthn:registration:error', (event) => { console.error('Registration failed:', event.detail); alert('Could not register authenticator: ' + event.detail.message); @@ -272,9 +257,8 @@ document.addEventListener('webauthn:registration:success', (event) => { console.log('Registration successful!'); }); ``` -{% endcode %} -## See Also +## See also * [User Authentication](user-authentication.md) - Authenticate users with registered authenticators * [Additional Authenticators](additional-authenticators.md) - Manage multiple authenticators diff --git a/the-project/contributing.md b/docs/the-project/contributing.md similarity index 100% rename from the-project/contributing.md rename to docs/the-project/contributing.md diff --git a/the-project/demos.md b/docs/the-project/demos.md similarity index 88% rename from the-project/demos.md rename to docs/the-project/demos.md index a4191d6..1c9686e 100644 --- a/the-project/demos.md +++ b/docs/the-project/demos.md @@ -27,22 +27,18 @@ They use `web-auth/webauthn-lib` alone: no framework, no database, no build step Run one of them from its own directory: -{% code lineNumbers="true" %} -```bash +```bash linenums="1" cd basic-demo ./same-origin/run.sh ``` -{% endcode %} The script installs the dependencies on first run and serves the demo on [http://localhost:8000](http://localhost:8000), which the browsers treat as a secure context. Set the `PORT` variable to serve it elsewhere. ## Running them all at once -{% code lineNumbers="true" %} -```bash +```bash linenums="1" ./run-all.sh ``` -{% endcode %} Every demo then gets its own port, from 8101 to 8110, so they can be compared side by side. They share the `localhost` Relying Party ID, which means a passkey registered in one of them is offered by the others. @@ -50,6 +46,6 @@ Every demo then gets its own port, from 8101 to 8110, so they can be compared si [symfony-demo](https://github.com/web-auth/webauthn-demos/tree/main/symfony-demo) is a complete application built on the bundle, with FrankenPHP, AssetMapper and Tailwind. It targets Symfony 8.1 and PHP 8.4, and shows the firewall, the Doctrine entities, a custom options storage and the Metadata Statement services in one place. -{% hint style="info" %} -The pure PHP demos exercise features that are not released yet, so they require `web-auth/webauthn-framework` in its `5.4.x-dev` version. The Symfony application targets the released `^5.3`. -{% endhint %} +!!! info + + The pure PHP demos exercise features that are not released yet, so they require `web-auth/webauthn-framework` in its `5.4.x-dev` version. The Symfony application targets the released `^5.3`. diff --git a/the-project/installation.md b/docs/the-project/installation.md similarity index 82% rename from the-project/installation.md rename to docs/the-project/installation.md index 393878c..12f6541 100644 --- a/the-project/installation.md +++ b/docs/the-project/installation.md @@ -24,9 +24,9 @@ For the Stimulus controllers (frontend integration), pull them straight from npm php bin/console importmap:require @web-auth/webauthn-stimulus ``` -{% hint style="warning" %} -The legacy PHP package `web-auth/webauthn-stimulus` is deprecated since v5.3.0 and will be removed in 6.0.0. Install the controllers via npm instead. -{% endhint %} +!!! warning + + The legacy PHP package `web-auth/webauthn-stimulus` is deprecated since v5.3.0 and will be removed in 6.0.0. Install the controllers via npm instead. ## Requirements diff --git a/the-project/the-project.md b/docs/the-project/the-project.md similarity index 84% rename from the-project/the-project.md rename to docs/the-project/the-project.md index 97775b7..5533e2b 100644 --- a/the-project/the-project.md +++ b/docs/the-project/the-project.md @@ -10,7 +10,7 @@ The complete specification can be found on [the W3C dedicated page](https://www. This framework contains PHP libraries and Symfony bundle to allow developers to integrate that authentication mechanism into their web applications. -## Class, Constant and Property Names +## Class, constant and property names Naming things may be complicated. That’s why the following rule applies on the whole framework: the name of classes, constants and properties are identical to the ones you will find in the specification. @@ -55,15 +55,15 @@ As an example, the [section 5.2.2 “Web Authentication Assertion”](https://ww * Conditional Create (new in 5.3.0) * Auto-register credentials after password authentication -## Compatible Authenticators +## Compatible authenticators As of January 2023, our framework supports every authenticator with full feature and algorithm support, achieving a 100% success rate across all tests. Official FIDO Alliance testing tools have validated its compliance. We ensure continuous conformity through rigorous unit and functional testing throughout the development process. -{% hint style="info" %} -See [https://github.com/herrjemand/awesome-webauthn/pull/61](https://github.com/herrjemand/awesome-webauthn/pull/61) and [https://github.com/herrjemand/awesome-webauthn#server-libs](https://github.com/herrjemand/awesome-webauthn?#server-libraries) -{% endhint %} +!!! info + + See [https://github.com/herrjemand/awesome-webauthn/pull/61](https://github.com/herrjemand/awesome-webauthn/pull/61) and [https://github.com/herrjemand/awesome-webauthn#server-libs](https://github.com/herrjemand/awesome-webauthn?#server-libraries) ## Support @@ -79,6 +79,6 @@ If you feel comfortable writing code, you could try to fix [opened issues where Do not forget to follow [these best practices](contributing.md). -{% hint style="danger" %} -If you think you have found a security issue, **DO NOT open an issue**. [You MUST submit your issue here](https://github.com/web-auth/webauthn-framework/security). -{% endhint %} +!!! danger + + If you think you have found a security issue, **DO NOT open an issue**. [You MUST submit your issue here](https://github.com/web-auth/webauthn-framework/security). diff --git a/the-project/web-browser-support.md b/docs/the-project/web-browser-support.md similarity index 95% rename from the-project/web-browser-support.md rename to docs/the-project/web-browser-support.md index 393b014..3e06afa 100644 --- a/the-project/web-browser-support.md +++ b/docs/the-project/web-browser-support.md @@ -2,7 +2,7 @@ description: Adoption by web browsers --- -# Web Browser Support +# Web browser support WebAuthn is now supported by all main web browsers: diff --git a/webauthn-in-a-nutshell/attestation-and-metadata-statement.md b/docs/webauthn-in-a-nutshell/attestation-and-metadata-statement.md similarity index 70% rename from webauthn-in-a-nutshell/attestation-and-metadata-statement.md rename to docs/webauthn-in-a-nutshell/attestation-and-metadata-statement.md index 006102b..5808092 100644 --- a/webauthn-in-a-nutshell/attestation-and-metadata-statement.md +++ b/docs/webauthn-in-a-nutshell/attestation-and-metadata-statement.md @@ -1,14 +1,14 @@ -# Metadata Statement +# Metadata statement -{% hint style="info" %} -This page is about an advanced feature. You can skip it if you are new with WebAuthn. -{% endhint %} +!!! info -{% hint style="danger" %} -Disclaimer: you **should not** ask for the Attestation Statement unless you want to play with it or you are working on an application that requires a high level of trust (e.g. Banking/Financial Company, Government Agency...). -{% endhint %} + This page is about an advanced feature. You can skip it if you are new with WebAuthn. -## Attestation Statement +!!! danger + + Disclaimer: you **should not** ask for the Attestation Statement unless you want to play with it or you are working on an application that requires a high level of trust (e.g. Banking/Financial Company, Government Agency...). + +## Attestation statement During the Attestation Ceremony (i.e. the registration of the authenticator), you can ask for the Attestation Statement of the authenticator. The Attestation Statements have one of the following types: @@ -19,12 +19,12 @@ During the Attestation Ceremony (i.e. the registration of the authenticator), yo * **Anonymization CA** (`AnonCA`): Authenticators use an Anonymization CA, which dynamically generates per-credential attestation certificates such that the attestation statements presented to Relying Parties do not provide uniquely identifiable information. * **Compound Attestation** (new in 5.3.0): A combination of multiple attestation statements that allows authenticators to provide multiple levels of attestation in a single response. -## Metadata Statement +## Metadata statement The Metadata Statements are issued by the manufacturers of the authenticators. These statements contain details about the authenticators (supported algorithms, biometric capabilities...) and all the necessary information to verify the Attestation Statements generated during the attestation ceremony. There are several possible sources to get these Metadata Statements. The main source is the [FIDO Alliance Metadata Service](https://fidoalliance.org/metadata) that allows fetching statements on-demand, but some of them may be provided by other means. -{% hint style="danger" %} -The FIDO Alliance Metadata Service provides a limited number of Metadata Statements. It is mandatory to get the statement from the manufacturer of your authenticators otherwise the Attestation Statement won't be verified and the Attestation Ceremony will fail. -{% endhint %} +!!! danger + + The FIDO Alliance Metadata Service provides a limited number of Metadata Statements. It is mandatory to get the statement from the manufacturer of your authenticators otherwise the Attestation Statement won't be verified and the Attestation Ceremony will fail. diff --git a/webauthn-in-a-nutshell/authenticators.md b/docs/webauthn-in-a-nutshell/authenticators.md similarity index 74% rename from webauthn-in-a-nutshell/authenticators.md rename to docs/webauthn-in-a-nutshell/authenticators.md index c210300..8addb2f 100644 --- a/webauthn-in-a-nutshell/authenticators.md +++ b/docs/webauthn-in-a-nutshell/authenticators.md @@ -8,27 +8,30 @@ An Authenticator is a cryptographic entity used to generate a public key credent Authenticators may have additional features such as PIN code or biometric sensors (fingerprint, facial recognition…) that offer user verification. -![USB device with fingerprint reader](<../.gitbook/assets/images (1).jpeg>) +![USB device with fingerprint reader](../assets/images/images-1.jpeg) -## Roaming Authenticators +## Roaming authenticators The roaming authenticator may have different forms. The most common form is a USB device the user plugs into their computer. It can be a paired Bluetooth device or a card with NFC capabilities. Authenticators of this class are removable from, and can "roam" among, client devices. -![Webauthn compatible devices](../.gitbook/assets/fido2.jpeg) +![Webauthn compatible devices](../assets/images/fido2.jpeg) -## Platform Authenticators +## Platform authenticators A platform authenticator is usually not removable from the client device. For example an Android smartphone or a Windows 10 computer with the associated security chips can act as an authenticator. -![Android screenshot](<../.gitbook/assets/webauthn-android-fennec (1).png>) +![Android screenshot](../assets/images/webauthn-android-fennec-1.png) -![Fingerprint reader on a laptop](../.gitbook/assets/master.jpg) +![Fingerprint reader on a laptop](../assets/images/master.jpg) ### Passkey -

Passkey Logo

+
+ ![Passkey Logo](../assets/images/fido-passkey-mark-a-black-e1702581853266.png){ width="188" } +
Passkey Logo
+
Passkeys are virtual authenticators that have extended capabilities and in particular: diff --git a/webauthn-in-a-nutshell/ceremonies.md b/docs/webauthn-in-a-nutshell/ceremonies.md similarity index 84% rename from webauthn-in-a-nutshell/ceremonies.md rename to docs/webauthn-in-a-nutshell/ceremonies.md index ec989d9..d7f1543 100644 --- a/webauthn-in-a-nutshell/ceremonies.md +++ b/docs/webauthn-in-a-nutshell/ceremonies.md @@ -6,11 +6,11 @@ description: Registration and Authentication process overview In WebAuthn, authentication workflows are called "ceremonies" - a term that emphasizes the formal, cryptographic nature of these operations. There are two main ceremonies that form the foundation of WebAuthn authentication. -## The Two Ceremonies +## The two ceremonies WebAuthn defines two distinct ceremonies: -### 1. Attestation Ceremony (Registration) +### 1. Attestation ceremony (registration) **Also called:** Creation Ceremony, Registration Ceremony @@ -21,7 +21,7 @@ WebAuthn defines two distinct ceremonies: * Adding an additional authenticator to an existing account * Registering a backup security key -### 2. Assertion Ceremony (Authentication) +### 2. Assertion ceremony (authentication) **Also called:** Request Ceremony, Authentication Ceremony, Login Ceremony @@ -32,11 +32,11 @@ WebAuthn defines two distinct ceremonies: * Step-up authentication for sensitive operations * Re-authentication after session timeout -## Common Workflow Pattern +## Common workflow pattern Both ceremonies follow a similar two-step pattern: -### Step 1: Options Creation +### Step 1: Options creation The Relying Party (your server) creates options that: * Define parameters for the operation @@ -44,7 +44,7 @@ The Relying Party (your server) creates options that: * Specify requirements and preferences * Are sent to the browser/authenticator -### Step 2: Response Verification +### Step 2: Response verification The authenticator: * Prompts the user for interaction @@ -52,22 +52,21 @@ The authenticator: * Returns a signed response * The server validates the response -{% hint style="info" %} -User interaction varies based on authenticator capabilities and your configuration. It can range from a simple button touch to biometric authentication (fingerprint, facial recognition, PIN code). -{% endhint %} +!!! info -## Attestation Ceremony (Registration) + User interaction varies based on authenticator capabilities and your configuration. It can range from a simple button touch to biometric authentication (fingerprint, facial recognition, PIN code). + +## Attestation ceremony (registration) The attestation ceremony registers a new authenticator credential for a user. -![The attestation ceremony](<../.gitbook/assets/registration (2).png>) +![The attestation ceremony](../assets/images/registration-2.png) -### Attestation Flow Breakdown +### Attestation flow breakdown -#### Server Side (Step 1) +#### Server side (step 1) -{% code lineNumbers="true" %} -```php +```php linenums="1" // 1. Create PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create( rp: $rpEntity, // Your application identity @@ -83,9 +82,8 @@ $session->set('webauthn_creation_challenge', $publicKeyCredentialCreationOptions // 3. Send options to browser as JSON return new JsonResponse($publicKeyCredentialCreationOptions); ``` -{% endcode %} -#### Client Side (Browser) +#### Client side (browser) ```javascript // 1. Receive options from server @@ -103,10 +101,9 @@ await fetch('/webauthn/register', { }); ``` -#### Server Side (Step 2) +#### Server side (step 2) -{% code lineNumbers="true" %} -```php +```php linenums="1" // 1. Load the attestation response $publicKeyCredential = $serializer->deserialize( $request->getContent(), @@ -124,9 +121,8 @@ $credentialSource = $attestationValidator->check( // 3. Save the new credential $credentialRepository->saveCredentialSource($credentialSource); ``` -{% endcode %} -### What Happens During Attestation? +### What happens during attestation? 1. **Challenge Generation**: Server creates a random challenge to prevent replay attacks 2. **User Prompt**: Browser asks user to interact with authenticator @@ -135,7 +131,7 @@ $credentialRepository->saveCredentialSource($credentialSource); 5. **Verification**: Server verifies signatures and stores the public key 6. **Storage**: Credential is saved and associated with the user account -### Attestation Data Contains +### Attestation data contains * **Public Key**: Used to verify future signatures * **Credential ID**: Unique identifier for this credential @@ -143,18 +139,17 @@ $credentialRepository->saveCredentialSource($credentialSource); * **Signature Counter**: Used to detect cloned authenticators * **Attestation Statement**: Optional proof of authenticator authenticity -## Assertion Ceremony (Authentication) +## Assertion ceremony (authentication) The assertion ceremony authenticates a user with a previously registered credential. -![The assertion ceremony](../.gitbook/assets/login.png) +![The assertion ceremony](../assets/images/login.png) -### Assertion Flow Breakdown +### Assertion flow breakdown -#### Server Side (Step 1) +#### Server side (step 1) -{% code lineNumbers="true" %} -```php +```php linenums="1" // 1. Get user's registered credentials $credentials = $credentialRepository->findAllForUserEntity($userEntity); $allowedCredentials = array_map( @@ -175,9 +170,8 @@ $session->set('webauthn_request_challenge', $publicKeyCredentialRequestOptions-> // 4. Send options to browser return new JsonResponse($publicKeyCredentialRequestOptions); ``` -{% endcode %} -#### Client Side (Browser) +#### Client side (browser) ```javascript // 1. Receive options from server @@ -195,10 +189,9 @@ await fetch('/webauthn/login', { }); ``` -#### Server Side (Step 2) +#### Server side (step 2) -{% code lineNumbers="true" %} -```php +```php linenums="1" // 1. Load the assertion response $publicKeyCredential = $serializer->deserialize( $request->getContent(), @@ -226,9 +219,8 @@ $credentialRepository->saveCredentialSource($updatedCredential); // 5. Log the user in $session->set('user_id', $userEntity->id); ``` -{% endcode %} -### What Happens During Assertion? +### What happens during assertion? 1. **Challenge Generation**: Server creates a new random challenge 2. **Credential Selection**: Browser identifies available credentials @@ -238,14 +230,14 @@ $session->set('user_id', $userEntity->id); 6. **Counter Check**: Server verifies counter to detect cloning 7. **Authentication**: If valid, user is logged in -### Assertion Data Contains +### Assertion data contains * **Authenticator Data**: Flags, counter, extensions * **Client Data**: Origin, challenge, type * **Signature**: Cryptographic proof using the private key * **User Handle**: Optional user identifier (for usernameless authentication) -## Key Differences Between Ceremonies +## Key differences between ceremonies | Aspect | Attestation (Registration) | Assertion (Authentication) | |--------|---------------------------|---------------------------| @@ -257,14 +249,13 @@ $session->set('user_id', $userEntity->id); | **Validator Class** | `AuthenticatorAttestationResponseValidator` | `AuthenticatorAssertionResponseValidator` | | **User Experience** | "Register security key" | "Use security key to sign in" | -## Security Considerations +## Security considerations -### Challenge Uniqueness +### Challenge uniqueness Both ceremonies require a unique, random challenge: -{% code lineNumbers="true" %} -```php +```php linenums="1" // Always generate a fresh challenge $challenge = random_bytes(32); // 32 bytes = 256 bits of entropy @@ -272,14 +263,12 @@ $challenge = random_bytes(32); // 32 bytes = 256 bits of entropy // NEVER use predictable challenges (like timestamps) // NEVER skip challenge verification ``` -{% endcode %} -### Challenge Storage +### Challenge storage Store challenges temporarily in server-side sessions: -{% code lineNumbers="true" %} -```php +```php linenums="1" // Store during Step 1 (options creation) $session->set('webauthn_challenge', base64_encode($challenge)); $session->set('webauthn_challenge_expires', time() + 60); // 60 second timeout @@ -292,14 +281,12 @@ if (time() > $session->get('webauthn_challenge_expires')) { throw new \Exception('Challenge expired'); } ``` -{% endcode %} -### Origin Validation +### Origin validation Always verify the origin matches your domain: -{% code lineNumbers="true" %} -```php +```php linenums="1" // Server must verify origin during validation $credentialSource = $attestationValidator->check( $publicKeyCredential->response, @@ -307,18 +294,17 @@ $credentialSource = $attestationValidator->check( 'https://example.com' // Must match your actual domain ); ``` -{% endcode %} -## Common Pitfalls +## Common pitfalls -### ❌ Challenge Reuse +### ❌ Challenge reuse ```php // WRONG: Reusing the same challenge $challenge = 'my-static-challenge'; // Never do this! ``` -### ❌ Skipping Verification +### ❌ Skipping verification ```php // WRONG: Trusting client data without validation @@ -326,7 +312,7 @@ $credentialId = $_POST['credentialId']; $user = $userRepository->find($credentialId); // DON'T! ``` -### ❌ Incorrect Origin +### ❌ Incorrect origin ```php // WRONG: Verifying with wrong domain @@ -334,14 +320,13 @@ $attestationValidator->check($response, $options, 'http://localhost'); // But the actual domain is https://example.com ``` -## Best Practices +## Best practices -### ✅ Use Timeouts +### ✅ Use timeouts Set reasonable timeouts for user interaction: -{% code lineNumbers="true" %} -```php +```php linenums="1" $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create( rp: $rpEntity, user: $userEntity, @@ -349,14 +334,12 @@ $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create timeout: 60000 // 60 seconds in milliseconds ); ``` -{% endcode %} -### ✅ Handle Errors Gracefully +### ✅ Handle errors gracefully Provide helpful error messages: -{% code lineNumbers="true" %} -```php +```php linenums="1" try { $credentialSource = $attestationValidator->check(/* ... */); } catch (\Webauthn\Exception\InvalidDataException $e) { @@ -366,22 +349,19 @@ try { ], 400); } ``` -{% endcode %} -### ✅ Store Credential Metadata +### ✅ Store credential metadata Keep track of when and how credentials were registered: -{% code lineNumbers="true" %} -```php +```php linenums="1" $credential->createdAt = new \DateTimeImmutable(); $credential->lastUsedAt = null; $credential->name = 'My YubiKey'; // Allow users to name their keys $credential->transports = ['usb', 'nfc']; // Store transport hints ``` -{% endcode %} -## See Also +## See also * [Authenticators](authenticators.md) - Learn about different authenticator types * [User Verification](user-verification.md) - Understand UP vs UV flags diff --git a/webauthn-in-a-nutshell/extensions.md b/docs/webauthn-in-a-nutshell/extensions.md similarity index 81% rename from webauthn-in-a-nutshell/extensions.md rename to docs/webauthn-in-a-nutshell/extensions.md index 69b84f2..f8c1ee3 100644 --- a/webauthn-in-a-nutshell/extensions.md +++ b/docs/webauthn-in-a-nutshell/extensions.md @@ -1,12 +1,12 @@ # Extensions -{% hint style="info" %} -This page is about an advanced feature. You can skip it if you are new with WebAuthn. -{% endhint %} +!!! info + + This page is about an advanced feature. You can skip it if you are new with WebAuthn. WebAuthn extensions are optional features that extend the core WebAuthn specification to support additional use cases and capabilities. They allow authenticators and browsers to provide extra functionality beyond basic authentication. -## What Are Extensions? +## What are extensions? Extensions provide a standardized way to: @@ -15,20 +15,20 @@ Extensions provide a standardized way to: * Enable specific features like credential protection or large blob storage * Maintain backward compatibility with legacy systems (like U2F) -## How Extensions Work +## How extensions work Extensions work in both directions: 1. **Extension Inputs** - Sent from the Relying Party to the authenticator via the browser 2. **Extension Outputs** - Returned from the authenticator to the Relying Party after processing -{% hint style="warning" %} -**Important**: Authenticators may ignore extension inputs if they don't support them. Extension outputs are **not guaranteed** and your application should handle their absence gracefully. -{% endhint %} +!!! warning + + **Important**: Authenticators may ignore extension inputs if they don't support them. Extension outputs are **not guaranteed** and your application should handle their absence gracefully. -## Common WebAuthn Extensions +## Common WebAuthn extensions -### Standard Extensions +### Standard extensions The most commonly used WebAuthn extensions include: @@ -49,7 +49,7 @@ All standard extensions are listed in the **IANA WebAuthn Extensions Registry**: [https://www.iana.org/assignments/webauthn/webauthn.xhtml](https://www.iana.org/assignments/webauthn/webauthn.xhtml) -## Library Support +## Library support This library provides: @@ -57,16 +57,15 @@ This library provides: * ✅ **AppID extension** - Built-in support for U2F backward compatibility * ⚠️ **Custom extensions** - You need to implement extension handlers for other extensions -{% hint style="warning" %} -Most extensions require custom implementation. It is up to you to create the extension handlers depending on which extensions you want to support. -{% endhint %} +!!! warning -## Basic Usage Example + Most extensions require custom implementation. It is up to you to create the extension handlers depending on which extensions you want to support. + +## Basic usage example Here's how to add an extension input when creating credential options: -{% code lineNumbers="true" %} -```php +```php linenums="1" response->getAuthenticatorData()->extensions; @@ -103,16 +100,14 @@ if (isset($extensionOutputs['credProps']['rk'])) { echo $isDiscoverable ? 'Resident key created' : 'Non-resident key'; } ``` -{% endcode %} -## Practical Use Cases +## Practical use cases -### 1. U2F Migration (appid Extension) +### 1. U2F migration (appid extension) Allow existing U2F credentials to work with your WebAuthn implementation: -{% code lineNumbers="true" %} -```php +```php linenums="1" $extensions = AuthenticationExtensions::create([ AuthenticationExtension::create('appid', 'https://example.com') ]); @@ -122,14 +117,12 @@ $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( extensions: $extensions ); ``` -{% endcode %} -### 2. Credential Properties (credProps Extension) +### 2. Credential properties (credProps extension) Determine if a credential supports resident keys (usernameless authentication): -{% code lineNumbers="true" %} -```php +```php linenums="1" $extensions = AuthenticationExtensions::create([ AuthenticationExtension::create('credProps', true) ]); @@ -137,14 +130,12 @@ $extensions = AuthenticationExtensions::create([ // After registration, check: $rk = $extensionOutputs['credProps']['rk'] ?? false; ``` -{% endcode %} -### 3. Large Blob Storage (largeBlob Extension) +### 3. Large blob storage (largeBlob extension) Store additional encrypted data with the credential: -{% code lineNumbers="true" %} -```php +```php linenums="1" // Write blob during registration $extensions = AuthenticationExtensions::create([ AuthenticationExtension::create('largeBlob', [ @@ -153,18 +144,16 @@ $extensions = AuthenticationExtensions::create([ ]) ]); ``` -{% endcode %} -### 4. Pseudo-Random Function (prf Extension) +### 4. Pseudo-random function (prf extension) + +!!! info -{% hint style="info" %} -PRF extension support was added in version 5.2.0 -{% endhint %} + PRF extension support was added in version 5.2.0 The PRF (Pseudo-Random Function) extension allows you to generate cryptographic keys from authenticator secrets. This is particularly useful for encryption scenarios: -{% code lineNumbers="true" %} -```php +```php linenums="1" // Request PRF during authentication $extensions = AuthenticationExtensions::create([ AuthenticationExtension::create('prf', [ @@ -179,11 +168,10 @@ $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( extensions: $extensions ); ``` -{% endcode %} -## Implementation Considerations +## Implementation considerations -### Check Extension Support +### Check extension support Not all authenticators support all extensions. Always: @@ -191,7 +179,7 @@ Not all authenticators support all extensions. Always: 2. Have fallback behavior when extensions aren't supported 3. Don't rely on extensions for critical security features -### Custom Extension Handlers +### Custom extension handlers For extensions beyond `appid`, you'll need to implement: @@ -200,7 +188,7 @@ For extensions beyond `appid`, you'll need to implement: See the [Pure PHP Extensions Guide](../pure-php/advanced-behaviours/extensions.md) or [Symfony Bundle Extensions Guide](../symfony-bundle/advanced-behaviors/extensions.md) for detailed implementation instructions. -## When to Use Extensions +## When to use extensions Use extensions when you need: @@ -215,7 +203,7 @@ Avoid extensions if: * ❌ You need universal compatibility (not all devices support extensions) * ❌ You're just getting started with WebAuthn -## See Also +## See also * [Pure PHP Extensions Implementation](../pure-php/advanced-behaviours/extensions.md) - Detailed extension implementation * [Symfony Extensions Configuration](../symfony-bundle/advanced-behaviors/extensions.md) - Framework-specific setup diff --git a/webauthn-in-a-nutshell/user-verification.md b/docs/webauthn-in-a-nutshell/user-verification.md similarity index 85% rename from webauthn-in-a-nutshell/user-verification.md rename to docs/webauthn-in-a-nutshell/user-verification.md index ee8684a..0b85895 100644 --- a/webauthn-in-a-nutshell/user-verification.md +++ b/docs/webauthn-in-a-nutshell/user-verification.md @@ -2,11 +2,11 @@ description: Prove to me who you claim to be! --- -# User Verification +# User verification User verification is a WebAuthn feature that confirms the identity of the actual user, not just possession of the authenticator. It answers the question: "Is the person using this authenticator really the authorized user?" -## What is User Verification? +## What is user verification? User verification requires the user to prove their identity through: @@ -17,7 +17,7 @@ User verification requires the user to prove their identity through: The goal is to **distinguish individual users** and ensure that the person authenticating is the legitimate account owner, not just someone who stole or found the authenticator. -## User Presence vs User Verification +## User presence vs user verification It's important to understand the difference: @@ -33,7 +33,7 @@ It's important to understand the difference: **User Verification** goes further by confirming the specific identity of that person. -## User Verification Requirements +## User verification requirements You can configure the user verification requirement when creating credential options. There are three possible values: @@ -41,8 +41,7 @@ You can configure the user verification requirement when creating credential opt The operation **requires** user verification and will **fail** if the authenticator does not set the `UV` flag. -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\PublicKeyCredentialCreationOptions; @@ -57,7 +56,6 @@ $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create authenticatorSelection: $authenticatorSelection ); ``` -{% endcode %} **Use when:** * High-security applications (banking, healthcare) @@ -68,13 +66,11 @@ $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create The operation **prefers** user verification but will **not fail** if the authenticator doesn't provide it. -{% code lineNumbers="true" %} -```php +```php linenums="1" $authenticatorSelection = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED ); ``` -{% endcode %} This is the **recommended default** for most applications. It uses user verification when available but doesn't exclude authenticators that don't support it. @@ -87,13 +83,11 @@ This is the **recommended default** for most applications. It uses user verifica The operation **discourages** user verification to minimize disruption to the user flow. -{% code lineNumbers="true" %} -```php +```php linenums="1" $authenticatorSelection = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED ); ``` -{% endcode %} **Use when:** * Low-risk operations @@ -101,18 +95,17 @@ $authenticatorSelection = AuthenticatorSelectionCriteria::create( * Simple presence verification is sufficient * Testing or demonstration purposes -## Checking the UV Flag +## Checking the UV flag The `userVerification` setting in the credential options is a **request** sent to the authenticator, not a guarantee. The `CheckUserVerification` ceremony step enforces it against the options that were actually emitted to the client — so if those options were built with a weaker requirement than your profile (for example, because [Client Override Policy](../symfony-bundle/advanced-behaviors/client-override-policy.md) is configured to allow a client to lower it), the ceremony will pass even though no verification took place. -{% hint style="warning" %} -**Mandatory for sensitive operations.** Do not rely solely on `userVerification: required` in the profile to gate access to privileged actions (admin, payment, secret rotation, AAL2/AAL3 compliance). Always re-check the `UV` flag on the returned authenticator data after a successful ceremony. This is your final, authoritative signal that user verification actually occurred. -{% endhint %} +!!! warning + + **Mandatory for sensitive operations.** Do not rely solely on `userVerification: required` in the profile to gate access to privileged actions (admin, payment, secret rotation, AAL2/AAL3 compliance). Always re-check the `UV` flag on the returned authenticator data after a successful ceremony. This is your final, authoritative signal that user verification actually occurred. After authentication or registration, inspect the flag on the response's authenticator data and decide whether to proceed: -{% code lineNumbers="true" %} -```php +```php linenums="1" // $authenticatorResponse is the AuthenticatorAssertionResponse or // AuthenticatorAttestationResponse that was just successfully validated. $authenticatorData = $authenticatorResponse instanceof AuthenticatorAssertionResponse @@ -127,23 +120,19 @@ if (! $authenticatorData->isUserVerified()) { // Safe to proceed with the privileged operation ``` -{% endcode %} You can also read the raw flags if you need fine-grained checks (for example combining `UV` with `BS`/`BE` for backup-eligibility policies): -{% code lineNumbers="true" %} -```php +```php linenums="1" $flags = $authenticatorData->getFlags(); $uvFlag = (ord($flags) & 0x04) !== 0; // UV flag is bit 2 ``` -{% endcode %} -## Practical Examples +## Practical examples -### Example 1: High-Security Banking App +### Example 1: High-security banking app -{% code lineNumbers="true" %} -```php +```php linenums="1" // Require user verification for all operations $authenticatorSelection = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED @@ -157,12 +146,10 @@ $options = PublicKeyCredentialCreationOptions::create( authenticatorSelection: $authenticatorSelection ); ``` -{% endcode %} -### Example 2: General Web Application +### Example 2: General web application -{% code lineNumbers="true" %} -```php +```php linenums="1" // Prefer user verification but don't require it $authenticatorSelection = AuthenticatorSelectionCriteria::create( userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED @@ -176,21 +163,18 @@ $options = PublicKeyCredentialCreationOptions::create( authenticatorSelection: $authenticatorSelection ); ``` -{% endcode %} -### Example 3: Fast Re-authentication +### Example 3: Fast re-authentication -{% code lineNumbers="true" %} -```php +```php linenums="1" // For quick re-auth on already logged-in user, discourage UV $options = PublicKeyCredentialRequestOptions::create( challenge: random_bytes(32), userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED ); ``` -{% endcode %} -## Device Capabilities +## Device capabilities Different authenticators have different user verification capabilities: @@ -203,28 +187,27 @@ Different authenticators have different user verification capabilities: | **YubiKey Bio** | Fingerprint | | **Basic security keys** | None (only user presence) | -{% hint style="info" %} -When you set `userVerification: required`, only authenticators capable of user verification will be available to the user during registration or authentication. -{% endhint %} +!!! info + + When you set `userVerification: required`, only authenticators capable of user verification will be available to the user during registration or authentication. -## Best Practices +## Best practices -### For Registration +### For registration 1. **Use `preferred` as default** - Balances security and compatibility 2. **Use `required` for sensitive accounts** - Banking, healthcare, admin accounts 3. **Inform users** - Explain that biometric/PIN improves security -### For Authentication +### For authentication 1. **Match registration settings** - If registered with `required`, authenticate with `required` 2. **Allow `preferred` for convenience** - Users can choose faster auth if device supports it 3. **Document your policy** - Make clear what level of verification is expected -### Conditional Logic +### Conditional logic -{% code lineNumbers="true" %} -```php +```php linenums="1" // Require UV only for privileged operations $isAdminAction = ($user->getRole() === 'ROLE_ADMIN'); @@ -237,9 +220,8 @@ $options = PublicKeyCredentialRequestOptions::create( userVerification: $userVerification ); ``` -{% endcode %} -## Common Issues +## Common issues ### Issue: "User verification not supported" @@ -253,19 +235,18 @@ $options = PublicKeyCredentialRequestOptions::create( **Solution**: Use `discouraged` or `preferred` for routine operations, save `required` for sensitive actions. -## Security Considerations +## Security considerations * ✅ **Higher assurance** - UV provides stronger identity verification than UP alone * ✅ **Phishing resistant** - Even if an attacker steals the authenticator, they need the PIN/biometric * ⚠️ **Not foolproof** - Biometric systems can have false positives; PINs can be observed * ⚠️ **Device dependent** - Relying solely on UV limits compatible devices -## Constants Reference +## Constants reference The library provides convenient constants in the `AuthenticatorSelectionCriteria` class: -{% code lineNumbers="true" %} -```php +```php linenums="1" use Webauthn\AuthenticatorSelectionCriteria; // For registration (attestation) @@ -276,9 +257,8 @@ AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED // These same constants can be used for authentication (assertion) // in PublicKeyCredentialRequestOptions ``` -{% endcode %} -## See Also +## See also * [Authenticators](authenticators.md) - Learn about different authenticator types * [Authenticator Selection Criteria](../pure-php/advanced-behaviours/authenticator-selection-criteria.md) - Detailed configuration diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..9c195cb --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,240 @@ +site_name: WebAuthn Framework +site_description: WebAuthn and passkeys for PHP and Symfony. Registration and authentication ceremonies, the Symfony bundle and the Symfony UX components, after WebAuthn Level 3. +site_url: https://webauthn-doc.spomky-labs.com/ +site_author: Spomky-Labs +repo_url: https://github.com/web-auth/webauthn-framework +repo_name: web-auth/webauthn-framework +edit_uri: https://github.com/web-auth/doc/edit/5.3/docs/ + +docs_dir: docs +site_dir: site + +# The GitBook table of contents. It stays while GitBook serves the domain, and the navigation below says the +# same thing in the words of MkDocs. +exclude_docs: | + SUMMARY.md + +theme: + name: material + custom_dir: docs/overrides + language: en + # Space Grotesk and IBM Plex Mono are served from assets/fonts, declared in slabs.css: the theme loads + # no font of its own. + font: false + icon: + repo: fontawesome/brands/github + edit: material/pencil-outline + annotation: material/plus-circle + favicon: assets/brand/favicon.svg + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Follow the system theme + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: custom + toggle: + icon: material/weather-sunny + name: Switch to the dark theme + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: custom + toggle: + icon: material/weather-night + name: Switch to the light theme + features: + - navigation.instant + - navigation.instant.progress + - navigation.tracking + - navigation.sections + - navigation.indexes + - navigation.top + - navigation.footer + - toc.follow + - search.suggest + - search.highlight + - search.share + - content.code.copy + - content.code.annotate + - content.tabs.link + - content.tooltips + - content.action.edit + +# The tokens of the Slabs design kit, as they are, then the rules that apply them. +extra_css: + - assets/stylesheets/slabs-tokens.css + - assets/stylesheets/slabs.css + +plugins: + - search + # Every asset is served from this site. Should a page ever reference an external one, it is downloaded + # at build time: nothing a reader loads reaches a third party. + - privacy + - git-revision-date-localized: + enable_creation_date: false + type: date + fallback_to_build_date: true + - redirects: + redirect_maps: {} + +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - tables + - toc: + title: On this page + permalink: true + permalink_title: Link to this section + toc_depth: 3 + - pymdownx.betterem + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + # A PHP block of these pages starts without " + + + + + + Page not found · WebAuthn Framework + + + + + + + +
+

Looking for this page in the latest version…

+
+ +

Page not found

+

This page has moved or never existed. Search, or start from one of these:

+
+ + +
+ +
+
+ + diff --git a/site-root/CNAME b/site-root/CNAME new file mode 100644 index 0000000..63282d1 --- /dev/null +++ b/site-root/CNAME @@ -0,0 +1 @@ +webauthn-doc.spomky-labs.com