Debian 13.5 XFCE: FIDO2 + PIN Login Walkthrough

A practical, recovery-conscious guide for FIDO2 PIN + touch login on Debian XFCE using pam_u2f, a group-gated policy, a central mapping file, LightDM, sudo, optional su, and XFCE screen unlock.

Revision: v4.4. Greeter-based locking is treated as required for FIDO-only desktop use; token-removal behavior switches to the LightDM greeter; keyring handling and rollback cleanup are included.

Debian 13.5 / trixieXFCE + LightDMFIDO2 PIN + touchbreakglass password account preserved
Contents
  1. What are FIDO U2F and FIDO2?
  2. Authentication design used by this guide
  3. Assumptions, placeholders, and safety preconditions
  4. Command style in this guide
  5. Install required packages
  6. Set or verify the FIDO2 PIN
  7. Pre-PAM FIDO2/PIN test
  8. Create and populate the FIDO login group
  9. Create the central PAM mapping file
  10. Create a shared PAM include file
  11. Test PAM with a standalone service
  12. Configure LightDM graphical login
  13. Configure sudo
  14. Core FIDO login checkpoint
  15. Configure su, optional
  16. Configure XFCE lock behavior: use the LightDM greeter, not the raw screen locker
  17. Switch to the LightDM greeter when the FIDO key is removed
  18. Extended test matrix before reboot
  19. Disable Unix password login for FIDO users
  20. Optional: GNOME Keyring and Chrome/Chromium prompts
  21. Emergency rollback
  22. Option notes and policy choices
  23. References and source notes
  24. Lab experiment: pkexec FIDO2 passwordless authentication
  25. Fix auto-suspend: polkit rules for power management
  26. Fix NetworkManager authentication: polkit rules for network settings
Companion reference. Keep the FIDO2 Lab Reference open alongside this walkthrough — it collects the diagnostics, triage decisions, and rollback commands for every section in one place.

1. What are FIDO U2F and FIDO2?

FIDO U2F and FIDO2 are authentication standards from the FIDO Alliance that use public-key cryptography instead of reusable shared secrets. The private credential remains on the authenticator, such as a hardware security key, while the relying system stores only public registration data.

FIDO U2F

FIDO U2F, or Universal 2nd Factor, is the older model. It is primarily used as a second factor: the user enters a normal password first, then proves possession of the hardware token by touching or activating it.

FIDO2

FIDO2 is the newer model. It supports stronger authenticator behavior, including PIN-backed and potentially passwordless authentication. Depending on the token and software stack, FIDO2 can support a flow where the security key effectively replaces the account password.

FeatureFIDO U2FFIDO2
Common useSecond factor after a passwordPasswordless or strong hardware-backed login
Typical user flowPassword + token touchToken PIN and/or biometric + token touch
Protocol familyCTAP1 / U2FCTAP2 and WebAuthn
Linux PAM use in this guideNot the preferred targetFIDO2 PIN + physical touch for selected users
Important distinction: WebAuthn/FIDO2 phishing resistance is strongest in web authentication because the credential is bound to a specific relying-party origin. This walkthrough is about local Debian PAM login, not website login. The local benefit is hardware-backed, PIN-protected, touch-confirmed authentication for selected local accounts.

2. Authentication design used by this guide

This guide uses a group-gated local policy:

  • Users in fido-login authenticate with FIDO2 token PIN + physical touch only.
  • Users not in fido-login, such as a controlled breakglass/local-admin account, remain password-only.
  • FIDO users do not fall back to Unix password if FIDO fails.
Shared PAM policy snippet
auth [success=ignore default=1] pam_succeed_if.so quiet user ingroup fido-login
auth [success=done default=die] pam_u2f.so authfile=/etc/security/u2f_keys cue="FIDO users: enter token PIN, then touch token when it flashes" pinverification=1 userverification=0
Do not use sufficient with nouserok. That combination can accidentally allow unmapped users to bypass password authentication. This guide avoids that pattern.

3. Assumptions, placeholders, and safety preconditions

Replace these placeholders before running commands:

PlaceholderMeaning
FIDO_USERThe normal local user who will use the FIDO token.
BREAKGLASS_USERA separate, known-working, sudo-capable local account that remains password-only.
/dev/hidraw1The FIDO token device path shown by fido2-token -L. The number may differ on your system (hidraw0, hidraw2, etc.) depending on other HID devices present — always confirm with fido2-token -L rather than assuming hidraw1.
Do not proceed with PAM changes using your only sudo-capable account. Create and test a separate password-only sudo-capable local account first. Confirm it can log in and run sudo -v. Keep that account out of fido-login.
  • Keep a root-capable terminal or TTY recovery path available while testing.
  • Do not modify /etc/pam.d/login in this walkthrough. Console TTY password login is your recovery path.
  • Back up each PAM file before editing it. The commands below include backup steps.
  • Do not close your working session until LightDM, sudo, and screen unlock have all been tested.

4. Command style in this guide

Command blocks are copy-pasteable and do not include shell prompt characters. The label above each block tells you whether it is intended for a normal user shell, a sudo command, or a PAM snippet.

VirtualBox: enable USB passthrough for the FIDO token

If you are running Debian inside VirtualBox, the FIDO token must be explicitly passed through to the VM before the guest OS can see it. VirtualBox does not pass USB devices to a VM by default.

Do this before proceeding to section 5. Without a USB filter, commands like fido2-token -L will return no devices even if the token is physically inserted.
  1. With the VM powered off, open VirtualBox Manager and select your VM.
  2. Click Settings → USB.
  3. Make sure Enable USB Controller is checked. Choose USB 2.0 (EHCI) or USB 3.0 (xHCI) depending on your host and token. The xHCI controller requires the VirtualBox Extension Pack.
  4. Click the Add USB filter icon (plug with a green plus). Insert the FIDO token into the host first so it appears in the device list, then select it. This creates a filter that automatically routes that token to the VM whenever it is inserted.
  5. Click OK and start the VM.
  6. Once booted, insert the FIDO token and verify the guest sees it: Normal user shell
    lsusb
    fido2-token -L
    fido2-token -L should list a device path such as /dev/hidraw1: vendor model FIDO+U2F. If it returns nothing, confirm the USB filter is saved and the correct controller type is selected.
USB controller type: if the token still does not appear after adding the filter, try switching between USB 2.0 and USB 3.0 in the VM USB settings. Some tokens enumerate differently depending on the host port and controller.

5. Install required packages

Uses sudo
sudo apt update
sudo apt install libpam-u2f pamu2fcfg fido2-tools pamtester

6. Set or verify the FIDO2 PIN

Normal user shell
fido2-token -L

Example output:

/dev/hidraw1: KEY-ID FIDO+U2F
Normal user shell
fido2-token -I /dev/hidraw1

Set a PIN if needed:

Normal user shell
fido2-token -S /dev/hidraw1

Change an existing PIN if needed:

Normal user shell
fido2-token -C /dev/hidraw1
Brand-new or unverified token? Instructors have an acceptance-test helper, fido-token-test.sh, that detects a key, dumps its identity, and runs a live touch/crypto round-trip before you enroll it. Ask your instructor to vet a new token first — the tool and its safe reset/identity features live in the FIDO2 Lab Reference, not this walkthrough.

7. Pre-PAM FIDO2/PIN test

Before touching PAM, verify that the token accepts its PIN and requires physical presence.

Normal user shell
rm -rf /tmp/fido2-login-test
mkdir -p /tmp/fido2-login-test
cd /tmp/fido2-login-test
Normal user shell
echo "debian local login test challenge" | openssl sha256 -binary | base64 > cred_param
echo "localhost" >> cred_param
echo FIDO_USER >> cred_param
dd if=/dev/urandom bs=32 count=1 status=none | base64 >> cred_param
Normal user shell
fido2-cred -M -t pin=true -i cred_param /dev/hidraw1 | fido2-cred -V -o cred

Expected behavior for a KEY-ID FIDO+U2F style token: enter the FIDO2 PIN, watch for the LED/button to flash, then press the token button. The command should return successfully and create cred.

Normal user shell
ls -l cred
cat cred

If you intentionally enter a bad PIN, FIDO_ERR_PIN_INVALID is expected. If you run without -t pin=true and see FIDO_ERR_PIN_REQUIRED, rerun with -t pin=true.

Normal user shell
cd
rm -rf /tmp/fido2-login-test

8. Create and populate the FIDO login group

The fido-login group is the selector. Members use FIDO2; non-members use password auth.

Uses sudo
sudo groupadd --force fido-login
sudo usermod -aG fido-login FIDO_USER
id FIDO_USER
getent group fido-login
Log out and back in, or reboot, after adding the user to the group. Existing graphical sessions may not have the new supplemental group until a new login session starts.

9. Create the central PAM mapping file

Use a central mapping file instead of ~/.config/Yubico/u2f_keys. This avoids encrypted-home and graphical-locker timing problems.

Normal user shell
pamu2fcfg -N -u FIDO_USER > /tmp/u2f_keys-FIDO_USER
cat /tmp/u2f_keys-FIDO_USER

Expected: token PIN prompt, optional flashing/touch, and one mapping line beginning with FIDO_USER:.

Uses sudo
sudo install -o root -g fido-login -m 0640 /tmp/u2f_keys-FIDO_USER /etc/security/u2f_keys
rm -f /tmp/u2f_keys-FIDO_USER
sudo python3 -c 'from pathlib import Path; p=Path("/etc/security/u2f_keys"); p.write_text(p.read_text().rstrip()+"\n")'
sudo ls -l /etc/security/u2f_keys
sudo grep "^FIDO_USER:" /etc/security/u2f_keys

Expected permissions:

-rw-r----- 1 root fido-login ... /etc/security/u2f_keys
Why 0640 root:fido-login? Some graphical unlockers run PAM in the user’s session context, not as root. A root-only 0600 mapping file can work under sudo pamtester but fail during actual screen unlock. The mapping file contains public credential registration data, not the token private key, so group-readable by fido-login is appropriate for this design.

Enroll a backup token

Insert the backup token, then append a second credential to the same user line.

Uses sudo and normal user commands
sudo cp /etc/security/u2f_keys /tmp/u2f_keys-FIDO_USER.new
sudo chown FIDO_USER:fido-login /tmp/u2f_keys-FIDO_USER.new
chmod 0640 /tmp/u2f_keys-FIDO_USER.new
printf ':' >> /tmp/u2f_keys-FIDO_USER.new
pamu2fcfg -N -n >> /tmp/u2f_keys-FIDO_USER.new
sudo install -o root -g fido-login -m 0640 /tmp/u2f_keys-FIDO_USER.new /etc/security/u2f_keys
rm -f /tmp/u2f_keys-FIDO_USER.new
sudo python3 -c 'from pathlib import Path; p=Path("/etc/security/u2f_keys"); p.write_text(p.read_text().rstrip()+"\n")'
sudo ls -l /etc/security/u2f_keys

10. Create a shared PAM include file

Instead of copying the same FIDO logic into every PAM service, put it in one reusable include file.

Control-flow note: This file intentionally uses success=done. Use PAM include/@include, not substack, so successful FIDO authentication stops before password auth.
Backup if file exists, then write file
sudo test -e /etc/pam.d/fido2-local-auth && sudo cp -a /etc/pam.d/fido2-local-auth /etc/pam.d/fido2-local-auth.bak || true
sudo tee /etc/pam.d/fido2-local-auth >/dev/null <<'EOF'
{{EMBED:config/fido2-local-auth}}
EOF

Use one of these include styles depending on the surrounding PAM file:

Debian @include style
@include fido2-local-auth
@include common-auth
Typed auth include style
auth include fido2-local-auth
auth include common-auth

11. Test PAM with a standalone service

Create a temporary PAM service before editing LightDM, sudo, su, or screen-locker files.

Backup if file exists, then write test service
sudo test -e /etc/pam.d/fido2-test && sudo cp -a /etc/pam.d/fido2-test /etc/pam.d/fido2-test.bak || true
sudo tee /etc/pam.d/fido2-test >/dev/null <<'EOF'
{{EMBED:config/fido2-test}}
EOF
Uses sudo
sudo pamtester fido2-test FIDO_USER authenticate
sudo pamtester fido2-test BREAKGLASS_USER authenticate

Expected: FIDO_USER gets FIDO PIN + touch only. BREAKGLASS_USER gets password only.

If pamtester says “Authentication service cannot retrieve authentication info”

Diagnostic commands
whoami
id FIDO_USER
getent group fido-login
sudo ls -l /etc/security/u2f_keys
sudo grep "^FIDO_USER:" /etc/security/u2f_keys
sudo cat /etc/pam.d/fido2-local-auth
sudo cat /etc/pam.d/fido2-test

None of those show pam_u2f's own reason for a failure — that goes to the journal, tagged by module name regardless of which program (pamtester, LightDM, sudo) triggered it:

Uses sudo
sudo journalctl -g pam_u2f --since "5 minutes ago"
Why -g instead of -u. pamtester and LightDM aren't systemd units the way ssh.service is, so journalctl -u has nothing to filter on here. -g (--grep) searches the message text directly, so it finds pam_u2f's log line no matter what process asked PAM to authenticate. See the PAM Primer's journalctl section for the general technique.

12. Configure LightDM graphical login

Edit /etc/pam.d/lightdm, not /etc/pam.d/lightdm-greeter, for normal user login authentication.

Backup, then edit
sudo cp -a /etc/pam.d/lightdm /etc/pam.d/lightdm.bak
sudo editor /etc/pam.d/lightdm

Find the service’s existing password include, usually:

@include common-auth

Insert the shared FIDO include immediately above it. Preserve unrelated lines such as pam_nologin, pam_env, SELinux, limits, loginuid, and keyring lines.

PAM snippet
# FIDO2 local authentication policy
@include fido2-local-auth

@include common-auth

Do not modify lightdm-greeter on the first pass

/etc/pam.d/lightdm-greeter normally permits the greeter process itself to start. It is not the normal user-authentication path.

Typical greeter line
auth      required pam_permit.so

Optional: prefer username-first graphical login

The group-gated PAM design needs to know the username before it selects the FIDO or password path. LightDM supports manual username entry when the greeter supports it.

Uses sudo
sudo mkdir -p /etc/lightdm/lightdm.conf.d
sudo tee /etc/lightdm/lightdm.conf.d/50-manual-login.conf >/dev/null <<'EOF'
{{EMBED:config/50-manual-login.conf}}
EOF

Expected presentation:

Username:
Password/PIN:

Pre-logout LightDM PAM test

Uses sudo
sudo pamtester lightdm FIDO_USER authenticate
sudo pamtester lightdm BREAKGLASS_USER authenticate

LightDM GTK greeter prompt quirk

LightDM GTK greeter may display blank prompt lines for the FIDO/PAM conversation until Enter is pressed. If the greeter shows blank lines instead of a clear PIN/password prompt, press Enter once. Then enter the FIDO token PIN and touch the token when it flashes.

LightDM prompt quirk: LightDM GTK greeter may display blank prompt lines for the FIDO/PAM conversation until Enter is pressed. If the greeter shows blank lines instead of a clear PIN/password prompt, press Enter once. Then enter the FIDO token PIN and touch the token when it flashes. Password-only users should still authenticate with their normal Unix password.

13. Configure sudo

Backup, then edit
sudo cp -a /etc/pam.d/sudo /etc/pam.d/sudo.bak
sudo editor /etc/pam.d/sudo

Place the shared FIDO include immediately before @include common-auth.

PAM snippet
session    required   pam_limits.so

@include fido2-local-auth
@include common-auth
@include common-account
@include common-session-noninteractive
Test
sudo -k
sudo -v
Warning: sudo package updates can overwrite /etc/pam.d/sudo. The sudo package owns /etc/pam.d/sudo as a dpkg conffile. Unattended or non-interactive upgrades — including unattended-upgrades, automated patching, or accepting a "replace modified conffile" prompt at an apt prompt — can silently replace your edited file with the package default, removing @include fido2-local-auth and disabling FIDO for sudo with no warning. Complete section 13.1 before proceeding.

13.1 Protect the sudo PAM configuration from package updates

Install an apt post-invoke hook that checks for the FIDO include after every apt invocation and re-adds it if a package upgrade removed it.

Step 1: Create the guard script.

Uses sudo
sudo tee /usr/local/sbin/fido2-sudo-pam-guard >/dev/null <<'EOF'
{{EMBED:scripts/fido2-sudo-pam-guard}}
EOF
sudo chmod 755 /usr/local/sbin/fido2-sudo-pam-guard

Step 2: Install the apt hook.

Uses sudo
sudo tee /etc/apt/apt.conf.d/99-fido2-sudo-pam-guard >/dev/null <<'EOF'
{{EMBED:config/99-fido2-sudo-pam-guard}}
EOF

Step 3: Verify the guard works before relying on it.

Test
# Confirm the include is present
sudo grep fido2-local-auth /etc/pam.d/sudo

# Simulate removal, run the guard, confirm it re-adds the line
sudo sed -i '/fido2-local-auth/d' /etc/pam.d/sudo
sudo /usr/local/sbin/fido2-sudo-pam-guard
sudo grep fido2-local-auth /etc/pam.d/sudo

# Confirm sudo still requires FIDO
sudo -k
sudo -v
How the hook works: After every apt invocation, the hook checks whether @include fido2-local-auth is present in /etc/pam.d/sudo. If a package upgrade removed it, the script re-inserts it immediately before @include common-auth and logs the event via logger. The hook is a no-op when the include is already present. Trade-off: if a future sudo upgrade restructures the file and removes @include common-auth as the marker, the re-insertion will silently fail — check /var/log/syslog for fido2-sudo-pam-guard entries after major upgrades.
If sudo is already broken after an upgrade: You can still run sudo using your password (the FIDO include is gone but common-auth falls back to password). Re-run the guard manually — sudo /usr/local/sbin/fido2-sudo-pam-guard — then confirm with sudo -k && sudo -v that FIDO is active again.
If your Unix password is also disabled (section 19), a sudo overwrite is a lockout. In later sections this guide locks the FIDO user's Unix password with passwd -l. If a package upgrade removes the FIDO include and the password is locked, common-auth has nothing to fall back to — the FIDO user cannot run sudo at all. Your only recovery path is the password-only breakglass account. This is exactly why the breakglass account must remain password-capable, must stay outside the fido-login group, and must never have its password locked.

14. Core FIDO login checkpoint

At this point, the core FIDO login design is complete only if the standalone PAM test, LightDM test, breakglass test, and real sudo -v test all pass.

Do not log out, reboot, or lock the FIDO user's Unix password yet. Keep a root-capable shell open until the greeter, sudo, breakglass login, and lock behavior have all been tested.
Core testCommand or actionExpected result
FIDO test servicesudo pamtester fido2-test FIDO_USER authenticateFIDO PIN + touch only.
Breakglass test servicesudo pamtester fido2-test BREAKGLASS_USER authenticatePassword only.
LightDM FIDO usersudo pamtester lightdm FIDO_USER authenticateFIDO PIN + touch only.
LightDM breakglass usersudo pamtester lightdm BREAKGLASS_USER authenticatePassword only.
Real sudo from FIDO sessionsudo -k, then sudo -vFIDO PIN + touch from the actual FIDO_USER session.

The remaining sections are desktop-integration and hardening steps: optional su, required greeter-based lock behavior, token-removal greeter switching, password locking, keyring behavior, and rollback.

15. Configure su, optional

This is optional. Preserve pam_rootok.so before the FIDO include so root-originated su keeps normal behavior.

Backup, then edit
sudo cp -a /etc/pam.d/su /etc/pam.d/su.bak
sudo editor /etc/pam.d/su

The important ordering is:

PAM snippet
auth       sufficient pam_rootok.so

@include fido2-local-auth
@include common-auth
Test
su - FIDO_USER
su - BREAKGLASS_USER

16. Configure XFCE lock behavior: use the LightDM greeter, not the raw screen locker

The standard XFCE screen-locker path is not reliable for this FIDO-only login design. The required workstation-lock design is to send the user to the LightDM greeter instead of relying on the xfce4-screensaver unlock dialog as the primary FIDO unlock UI.

Tested behavior: configuring xflock4 to call dm-tool switch-to-greeter avoids the xfce4-screensaver FIDO-only flicker problem and preserves access to the password-only breakglass account.

Design rule: do not use the raw xfce4-screensaver unlock dialog as the normal FIDO unlock path. Use the LightDM greeter for manual lock, switch-user behavior, and token-removal locking.

Keep the XFCE screensaver PAM file sane as a fallback

Even though the final lock flow uses the LightDM greeter, keep /etc/pam.d/xfce4-screensaver valid. This prevents confusing PAM errors if XFCE still invokes the screensaver dialog during idle timeout or other edge cases.

Uses sudo
sudo cp -a /etc/pam.d/xfce4-screensaver /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth
sudo editor /etc/pam.d/xfce4-screensaver

Use this content:

PAM snippet
#%PAM-1.0

auth include fido2-local-auth
auth include common-auth
auth optional pam_gnome_keyring.so

account required pam_permit.so

The explicit account required pam_permit.so line avoids the observed pam_unix(xfce4-screensaver:account): setuid failed account-management problem.

Test the PAM service directly:

Uses sudo
sudo pamtester xfce4-screensaver FIDO_USER authenticate
sudo pamtester xfce4-screensaver FIDO_USER acct_mgmt
sudo pamtester xfce4-screensaver BREAKGLASS_USER authenticate
sudo pamtester xfce4-screensaver BREAKGLASS_USER acct_mgmt
TestExpected result
FIDO_USER authenticateFIDO PIN + touch.
FIDO_USER acct_mgmtSuccess.
BREAKGLASS_USER authenticatePassword.
BREAKGLASS_USER acct_mgmtSuccess.

Make xflock4 switch to the LightDM greeter

The xflock4 script first checks the XFCE session setting /general/LockCommand. Set that command to switch to the LightDM greeter.

Normal graphical session
xfconf-query -c xfce4-session -p /general/LockCommand --create -t string -s "dm-tool switch-to-greeter"

Verify the setting:

Normal graphical session
xfconf-query -c xfce4-session -p /general/LockCommand

Expected output:

Expected output
dm-tool switch-to-greeter

Test manual locking:

Normal graphical session
xflock4

Expected behavior:

  • The machine switches to the LightDM greeter instead of the xfce4-screensaver unlock dialog.
  • The FIDO user authenticates through LightDM with FIDO PIN + touch.
  • The breakglass user remains available with normal password authentication.
  • Switch-user behavior remains available at the greeter.

If raw xflock4 fails from a terminal

Raw xflock4 only works from the original graphical XFCE session environment. It needs DISPLAY, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS. A nested login shell such as su - FIDO_USER may strip those variables.

Normal graphical session
echo "$DISPLAY"
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"

If these are empty, exit the nested login shell and test from the original XFCE terminal session.

If idle locking still uses xfce4-screensaver

The xflock4 manual lock path may be fixed by /general/LockCommand, but idle timeout may still be controlled by xfce4-screensaver itself. This idle-timeout path must be tested on each installation before the setup is considered complete.

Check which lockers are running:

Normal graphical session
pgrep -a 'xfce4-screensaver|light-locker|xscreensaver|xsecurelock'

If xfce4-screensaver continues to present its own unlock dialog during idle timeout and flickers when the token is absent, prefer one of these approaches:

  • Disable xfce4-screensaver idle locking and rely on LightDM greeter switching for manual lock and token-removal lock.
  • Replace the active idle locker with a LightDM-friendly path.
  • Keep xfce4-screensaver installed but avoid using it as the normal FIDO unlock surface.

17. Switch to the LightDM greeter when the FIDO key is removed

This section sends the machine to the LightDM greeter when the FIDO token is removed. It depends on the greeter-based lock behavior configured and tested in the previous section. This is part of the intended desktop behavior for this FIDO-only setup, but it is still not an authentication control; the real authentication control remains the FIDO/PAM login policy.

Final design from testing: for this XFCE/LightDM setup, token removal should switch to the LightDM greeter rather than invoking xflock4 or forcing the xfce4-screensaver unlock dialog. The greeter path preserves access to user switching and the password-only breakglass account.
Why not just call xflock4? Calling xflock4 on token removal can trap the user inside the current session's FIDO-only unlock dialog. If the token is absent, the unlock dialog may flicker or repeatedly restart, and the Switch User path may not be available until the token is reinserted. Switching to the LightDM greeter avoids that failure mode.

17.1. Confirm the greeter switch command exists

Normal graphical session
command -v dm-tool

Expected result:

Example output
/usr/bin/dm-tool

From the real XFCE terminal session, test switching to the greeter:

Normal graphical session
dm-tool switch-to-greeter

Expected behavior: the session switches to the LightDM greeter. From there, the FIDO user should be able to authenticate with FIDO PIN + touch, and the breakglass user should be able to authenticate with a normal password.

Greeter prompt quirk: LightDM GTK greeter may initially show blank prompt lines for the FIDO/PAM conversation. If no prompt text is visible, press Enter once. The FIDO prompt should then appear. Enter the token PIN, then touch the token when it flashes.

17.2. Find the token's udev identifiers

Insert the FIDO key, then monitor udev events:

Uses sudo
sudo udevadm monitor --udev --environment

Remove the FIDO key and look for the hidraw remove event. Prefer the event that includes ID_FIDO_TOKEN=1 and ID_SECURITY_TOKEN=1.

Example remove event
ACTION=remove
SUBSYSTEM=hidraw
DEVNAME=/dev/hidraw1
ID_FIDO_TOKEN=1
ID_SECURITY_TOKEN=1
ID_VENDOR_ID=096e
ID_MODEL_ID=0858
ID_MODEL=FIDO

Record ID_VENDOR_ID and ID_MODEL_ID. If your token exposes a truly unique serial number, you may also match on that, but some tokens expose a generic product serial string rather than a per-token serial.

17.3. Create the greeter-switch helper

This helper is run by root through udev/systemd, finds the active local graphical session, reconstructs the session environment, and runs dm-tool switch-to-greeter as the graphical user.

Uses sudo
sudo tee /usr/local/sbin/fido-remove-switch-to-greeter >/dev/null <<'EOF'
{{EMBED:scripts/fido-remove-switch-to-greeter}}
EOF

sudo chown root:root /usr/local/sbin/fido-remove-switch-to-greeter
sudo chmod 0755 /usr/local/sbin/fido-remove-switch-to-greeter

17.4. Test the helper manually

Uses sudo
sudo /usr/local/sbin/fido-remove-switch-to-greeter

Expected behavior: the session switches to the LightDM greeter. After returning, inspect logs:

Troubleshooting
sudo journalctl -b --since "3 minutes ago" | grep -Ei 'fido-switch-greeter|dm-tool|lightdm|greeter'

If dm-tool reports Not running inside a display manager, XDG_SEAT_PATH not defined, verify that the helper includes the XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0 environment variable.

17.5. Test a logging-only udev rule first

Before letting udev switch the greeter, confirm the rule fires on the correct remove event. Replace the vendor/model IDs with the values observed from your token.

Uses sudo
sudo tee /etc/udev/rules.d/85-fido-lock-on-remove-test.rules >/dev/null <<'EOF'
{{EMBED:config/85-fido-lock-on-remove-test.rules}}
EOF

sudo udevadm control --reload-rules

Remove the token, then check for the test log:

Troubleshooting
sudo journalctl -b --since "2 minutes ago" | grep -i fido-udev-test

If the test log appears, remove the test rule:

Uses sudo
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo udevadm control --reload-rules

17.6. Create the real udev removal rule

Do not run dm-tool directly from udev. udev jobs should be short-lived. Use systemd-run --no-block to hand the work off to systemd.

Uses sudo
sudo tee /etc/udev/rules.d/85-fido-lock-on-remove.rules >/dev/null <<'EOF'
{{EMBED:config/85-fido-lock-on-remove.rules}}
EOF

sudo udevadm control --reload-rules

17.7. Test token removal

  1. Stay logged into XFCE.
  2. Insert the FIDO key.
  3. Remove the FIDO key.
  4. The machine should switch to the LightDM greeter.
  5. Select the FIDO user and reinsert the token to authenticate with FIDO PIN + touch, or select the password-only breakglass user.

Check logs after the test:

Troubleshooting
sudo journalctl -b --since "3 minutes ago" | grep -Ei 'fido-switch-greeter|fido-udev|udev|dm-tool|lightdm|greeter'

17.8. Notes on raw xflock4 testing

Raw xflock4 only works from the original graphical XFCE session environment. It needs DISPLAY, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS. If those variables are empty, such as inside a nested su - login shell, raw xflock4 may fail even though the desktop session is healthy.

Graphical user session
echo "$DISPLAY"
echo "$XDG_RUNTIME_DIR"
echo "$DBUS_SESSION_BUS_ADDRESS"

The greeter-switch helper does not depend on the current shell environment; it reconstructs the graphical session environment from loginctl.

17.9. Rollback the removal-switch feature (optional)

Only perform this rollback if you do not need the token-removal locking feature. Section 18 uses FIDO key removal as one of the extended test matrix items. Do not roll back this feature before completing that test.
Uses sudo
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove.rules
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo rm -f /usr/local/sbin/fido-remove-switch-to-greeter
sudo udevadm control --reload-rules

18. Extended test matrix before reboot

TestCommand or actionExpected result
FIDO test servicesudo pamtester fido2-test FIDO_USER authenticateFIDO PIN + touch only.
Breakglass test servicesudo pamtester fido2-test BREAKGLASS_USER authenticatePassword only.
LightDM FIDO usersudo pamtester lightdm FIDO_USER authenticateFIDO PIN + touch only.
LightDM breakglass usersudo pamtester lightdm BREAKGLASS_USER authenticatePassword only.
Real sudo from FIDO sessionsudo -k, then sudo -vFIDO behavior for the actual FIDO_USER session.
su, if configuredsu - FIDO_USER and su - BREAKGLASS_USERCorrect path per account. If su includes fido2-local-auth, su - FIDO_USER may succeed by FIDO instead of password.
Manual lockxflock4Switches to the LightDM greeter. FIDO user authenticates through LightDM with PIN + touch; breakglass user remains available.
Idle-timeout lockWait for the configured idle lock timeout.Either switches to the LightDM greeter or uses a known-tested fallback. No flicker loop. Breakglass user remains reachable.
FIDO key removalRemove the FIDO token from an active graphical session.System switches to the LightDM greeter. User switching and breakglass login remain available.

Reboot only after tests pass

Uses sudo
sudo reboot

19. Disable Unix password login for FIDO users

After the FIDO login path, LightDM greeter lock behavior, sudo behavior, breakglass account, and recovery path have been fully tested, the FIDO-enabled user can be made token-only by locking that user's Unix password. This prevents password-based authentication for that user while keeping the account itself valid for normal login, group membership, sudo policy, and PAM account/session checks.

Do not do this until the full test matrix passes. Keep a separate sudo-capable BREAKGLASS_USER with a known-working password. Do not lock the breakglass account's password.
Recovery model change: after passwd -l FIDO_USER, console TTY password recovery for that user is no longer available. Your password-based recovery path is BREAKGLASS_USER.

Lock the password, not the account. Use passwd -l for the FIDO user. Do not expire the account, disable the shell, delete the user, or remove normal group memberships.

Uses sudo
sudo passwd -l FIDO_USER

Verify the password state:

Uses sudo
sudo passwd -S FIDO_USER
sudo getent shadow FIDO_USER

The shadow password field should begin with !, indicating that password authentication is locked for that account. The account should still exist and should still be a member of fido-login.

Verify group membership:

Normal user shell
id FIDO_USER
getent group fido-login

Retest after locking the password

Run the core tests again before logging out or rebooting. Prefer operational tests from the actual FIDO_USER graphical session over synthetic-only tests.

From the actual FIDO_USER graphical session
sudo -k
sudo -v
xflock4
Uses sudo
sudo pamtester lightdm FIDO_USER authenticate
sudo pamtester lightdm BREAKGLASS_USER authenticate
sudo pamtester xfce4-screensaver FIDO_USER authenticate
TestExpected result
FIDO_USER through LightDMFIDO PIN + touch only.
FIDO_USER through real sudo -vFIDO PIN + touch only.
xflock4Switches to the LightDM greeter, then FIDO PIN + touch through LightDM.
BREAKGLASS_USER through LightDMPassword only.

Confirm the Unix password itself is locked

If su has been modified to include fido2-local-auth, then su - FIDO_USER may still succeed through FIDO. That is expected. To prove that the Unix password itself is locked, test a password-only path such as an unmodified TTY login for FIDO_USER, or perform this check before enabling FIDO for su.

FIDO-protected PAM services should continue to work because fido2-local-auth exits successfully before falling through to common-auth.

Restore password login later, if needed

Uses sudo
sudo passwd -u FIDO_USER

Only unlock the password if you intentionally want password-based authentication restored for that account.

20. Optional: GNOME Keyring and Chrome/Chromium prompts

After converting a user to FIDO-only login, Chrome or Chromium may ask for the GNOME Keyring password when it tries to access stored browser secrets. This is expected.

GNOME Keyring is separate from FIDO/PAM login. GNOME Keyring normally unlocks the login keyring using the password entered during login. In the FIDO-only flow, the user authenticates with token PIN + touch, so there is no Unix password for pam_gnome_keyring.so to reuse.

This means FIDO2 can authenticate the desktop login, but it does not automatically decrypt an existing password-protected keyring. The keyring password remains separate.

20.1. Options

OptionBehaviorTradeoff
Keep the keyring passwordChrome/Chromium prompts for the keyring password when neededMost conservative; preserves separate protection for keyring secrets
Set the login keyring password blankChrome/Chromium stops prompting for the keyring passwordConvenient, but keyring secrets are no longer protected by a separate keyring password after session start
Restore password loginGNOME Keyring may auto-unlock from the login passwordUndermines the FIDO-only account goal
Security tradeoff: setting the keyring password blank is optional. It may be acceptable on a full-disk-encrypted laptop where the primary goal is removing reusable OS login passwords, but it reduces protection for secrets stored in the keyring during a logged-in session.

20.2. Install Seahorse

On a minimal XFCE install, the graphical keyring editor may not already be installed. Install Seahorse:

Uses sudo
sudo apt install seahorse
Dependency issues on minimal XFCE: on a minimal Debian/XFCE install, seahorse may fail to install due to unresolvable GNOME library dependencies. If apt reports dependency errors, try the following:

sudo apt install --fix-broken
sudo apt update && sudo apt full-upgrade

If dependencies still cannot be resolved, an alternative is to manage the keyring password from the command line using secret-tool (from the libsecret-tools package) or by deleting and recreating the keyring file at ~/.local/share/keyrings/login.keyring. Note that deleting the keyring file will erase all stored keyring secrets.

Launch it:

Normal graphical session
seahorse

20.3. Set the login keyring password blank

  1. Open Passwords and Keys / Seahorse.
  2. Find the Login keyring.
  3. Right-click the Login keyring and choose Change Password.
  4. Enter the current keyring password.
  5. Leave the new password fields blank.
  6. Accept the warning about storing passwords unencrypted, if this matches your threat model.

After this, Chrome/Chromium should stop asking for the keyring password when launched inside the FIDO-only user session.

20.4. Do not automate this step

This walkthrough treats blanking the keyring password as an explicit user choice. Do not automate it in the PAM migration script, because it changes how local application secrets are protected.

21. Emergency rollback

If a PAM change breaks authentication and you still have a root-capable shell, remove the shared include references first:

Uses sudo
sudo sed -i '/fido2-local-auth/d' /etc/pam.d/lightdm /etc/pam.d/sudo /etc/pam.d/su /etc/pam.d/xfce4-screensaver 2>/dev/null || true
sudo rm -f /etc/pam.d/fido2-local-auth
sudo rm -f /etc/lightdm/lightdm.conf.d/50-manual-login.conf
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove.rules
sudo rm -f /etc/udev/rules.d/85-fido-lock-on-remove-test.rules
sudo rm -f /usr/local/sbin/fido-remove-switch-to-greeter
sudo rm -f /usr/local/sbin/lock-on-fido-remove
sudo udevadm control --reload-rules

If you need to restore backed-up PAM files:

Uses sudo
sudo test -e /etc/pam.d/lightdm.bak && sudo cp -a /etc/pam.d/lightdm.bak /etc/pam.d/lightdm || true
sudo test -e /etc/pam.d/sudo.bak && sudo cp -a /etc/pam.d/sudo.bak /etc/pam.d/sudo || true
sudo test -e /etc/pam.d/su.bak && sudo cp -a /etc/pam.d/su.bak /etc/pam.d/su || true
sudo test -e /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth && sudo cp -a /etc/pam.d/xfce4-screensaver.bak.before-fido2-local-auth /etc/pam.d/xfce4-screensaver || true
sudo test -e /etc/pam.d/xfce4-screensaver.bak && sudo cp -a /etc/pam.d/xfce4-screensaver.bak /etc/pam.d/xfce4-screensaver || true

Restart display manager only when ready

Uses sudo
sudo systemctl restart lightdm

If you are locked out entirely, boot recovery or a live USB, mount the system, and remove the fido2-local-auth includes from the PAM files.

22. Option notes and policy choices

Common pam_u2f options used here

OptionMeaning
authfile=/etc/security/u2f_keysUse the central root-managed mapping file.
cue="..."Ask PAM/greeter to show a human cue. Graphical greeters may display it imperfectly.
pinverification=1Require the FIDO2 token PIN during authentication.
userverification=0Do not require a separate biometric or authenticator-level user-verification feature. The expected flow here is token PIN plus physical presence/touch.
PIN verification vs. user verification: In this walkthrough, pinverification=1 requires the token PIN. userverification=0 avoids requiring a separate biometric or authenticator-level UV feature. This is appropriate for KEY-ID FIDO+U2F style behavior where the desired flow is PIN + flashing-button touch.

pamu2fcfg options used here

OptionMeaning
-NEnroll a credential that requires PIN verification.
-u FIDO_USERCreate the mapping line for that user.
-nOutput only credential data so a backup token can be appended.

Why edit selected PAM services?

This guide edits LightDM, sudo, optional su, and the actual screen-locker PAM file. It does not edit common-auth globally, because a mistake there can break too many authentication paths at once.

Lock policy used here

This guide uses the LightDM greeter as the normal lock/unlock surface for FIDO users. The raw xfce4-screensaver unlock dialog is kept PAM-valid as a fallback, but it is not the preferred or tested primary unlock UI for FIDO-only use.

23. References and source notes

  • Debian packages: libpam-u2f, pamu2fcfg, fido2-tools, and pamtester.
  • Yubico pam_u2f manual: documents authfile, cue, pinverification, userverification, nouserok, and passwordless examples.
  • Yubico pamu2fcfg manual: documents -N and -n.
  • libfido2 fido2-token and fido2-cred manuals: document listing tokens, setting/changing PINs, and make-credential tests with -t pin=true.
  • pam_succeed_if manual: documents group-based selection with user ingroup.
  • LightDM common configuration and sample config: document greeter-hide-users and greeter-show-manual-login.
  • loginctl manual: documents lock-sessions.
  • Debian udev documentation: covers matching USB device add/remove events with udev rules.
LAB EXPERIMENT

24. Lab experiment: pkexec FIDO2 passwordless authentication

You have now configured FIDO2 PIN + touch for LightDM, sudo, optional su, and screen-lock. The next challenge is extending the same passwordless authentication policy to pkexec, the PolicyKit helper that graphical applications use to request elevated privileges.

Before you begin: confirm that your breakglass account (BREAKGLASS_USER) can still authenticate with a password and that you have an open root-capable shell. The PAM stack for polkit-1 is separate from sudo — a mistake here will not lock you out of sudo, but you should still have a fallback.

Background: why pkexec is different

pkexec is driven by PolicyKit (polkit), which calls PAM using the service name polkit-1. Unlike sudo, which presents a terminal conversation, pkexec may invoke a graphical authentication agent (such as polkit-gnome-authentication-agent-1) that prompts the user. The agent mediates the PAM conversation. This means:

  • The PAM file to edit is /etc/pam.d/polkit-1.
  • Whether the agent can relay a FIDO2 PIN prompt depends on how it handles the PAM conversation.
  • Token user-presence (the physical touch) is handled inside pam_u2f and does not require special agent support.

Your task

Working from what you learned in sections 10–13, configure pkexec so that FIDO_USER authenticates with FIDO2 PIN + touch (no Unix password) while BREAKGLASS_USER continues to authenticate with a password.

Guiding questions — think through these before touching any files:
  1. Which PAM file controls pkexec authentication? How would you verify this?
  2. What is the minimum change needed to insert the shared fido2-local-auth include into that file?
  3. How can you test the polkit-1 PAM service with pamtester before invoking a real graphical app that calls pkexec?
  4. If the graphical agent does not forward the PIN prompt correctly, what alternative PAM conversation path could you use? (Hint: consider what happens when no graphical agent is running.)
  5. If a GUI app triggers pkexec and the dialog never appears, how would you diagnose whether the problem is in the PAM stack, the polkit policy, or the agent?

Suggested approach

Work through these steps in order. Do not skip the pamtester step — it will tell you whether the PAM side is correct before you involve the graphical agent.

#StepGoal
1Locate and back up /etc/pam.d/polkit-1.Preserve a known-good copy before any edit.
2Read the current polkit-1 PAM file and identify where @include common-auth (or equivalent) appears.Know what you are inserting above.
3Insert @include fido2-local-auth immediately above the existing auth include, following the same pattern as sections 12–13.Apply the group-gated FIDO policy to pkexec.
4Run sudo pamtester polkit-1 FIDO_USER authenticate and sudo pamtester polkit-1 BREAKGLASS_USER authenticate.Confirm the PAM stack is correct without invoking a real graphical app yet.
5Trigger a real pkexec call from the FIDO_USER graphical session (for example, pkexec /usr/bin/id from a terminal, or open a graphical app that requires elevated privileges).Observe whether the agent relays the PIN prompt and whether FIDO touch is accepted.
6Record what you observe: does the PIN prompt appear? Where? Does the token flash? Does authentication succeed?Document actual behavior, not assumed behavior.
Note on the graphical agent: on a minimal XFCE install the polkit agent may be polkit-gnome-authentication-agent-1 or it may not be running at all. Run pgrep -a polkit to see what is active. If no agent is running, pkexec falls back to a terminal-based PAM conversation, which is much easier to observe.

Verification checklist

TestExpected resultPass?
sudo pamtester polkit-1 FIDO_USER authenticateFIDO PIN + touch only. No Unix password prompt.
sudo pamtester polkit-1 BREAKGLASS_USER authenticateUnix password only. No FIDO prompt.
pkexec /usr/bin/id as FIDO_USER (terminal or graphical)Authentication succeeds with PIN + touch. Output shows uid=0(root).
Intentionally enter a wrong PIN via pamtesterAuthentication fails. No fallback to password.
Remove token, then attempt pamtester polkit-1 FIDO_USER authenticateAuthentication fails cleanly. No password fallback.
Extension challenge (optional): some versions of polkit-1 on Debian ship with pam_permit.so as the only auth line — meaning any user is automatically authenticated for pkexec without a credential. If you observe this, discuss with your instructor why this is the default and what the correct production posture should be. Then apply the group-gated FIDO policy anyway, so that FIDO_USER is required to use FIDO2 and BREAKGLASS_USER falls through to common-auth.

25. Fix auto-suspend: polkit rules for power management

After configuring polkit-1 PAM in section 24, auto-suspend will stop working. The screen locks, the idle timer fires — and a polkit authentication prompt appears on the locked screen that the user cannot see and the graphical agent cannot complete. The system stays awake.

Why it fails

Both polkit actions that xfce4-power-manager uses for suspend have implicit active: yes — meaning polkit grants them without authentication when the session is active. The problem is that auto-suspend fires after the screen has locked. When light-locker activates, logind marks the session inactive. With an inactive session, both actions fall to auth_admin:

Actionimplicit activeimplicit inactive
org.freedesktop.login1.suspendyesauth_admin_keep
org.xfce.power.xfce4-pm-helperyesauth_admin

Polkit invokes the polkit-1 PAM stack to satisfy the auth_admin requirement. That stack now includes fido2-local-auth, which needs FIDO hardware. The graphical authentication agent cannot present a token challenge on a locked screen, so authentication fails and the suspend never completes.

xfce4-power-manager tries both action IDs in sequence — the logind action first, then its own setuid helper as a fallback. You can confirm both failures in the polkit journal immediately after a failed auto-suspend:

Diagnostic
sudo journalctl -u polkit --since "5 minutes ago"

Expected output: two FAILED to authenticate entries — one for org.freedesktop.login1.suspend and one for org.xfce.power.xfce4-pm-helper.

Why reboot and shutdown work: they are manually initiated while the session is still active. Auto-suspend is triggered automatically by xfce4-power-manager after idle time elapses — which happens after the screen has already locked. It is the session state at the moment of invocation that differs, not the polkit policy for those actions.

The fix: a polkit rules file

Polkit's authorization layer has its own override mechanism, entirely separate from PAM: JavaScript rules files in /etc/polkit-1/rules.d/. A rules file can return polkit.Result.YES for a matching action and subject, bypassing both the implicit policy and the PAM authentication check.

PAM vs. rules — two separate layers: section 24 configures polkit's PAM authentication layer — who must present what credential when polkit decides to challenge. This section configures polkit's authorization layer — which actions skip the challenge entirely for specific users. Both layers remain active and complementary. The rules file does not affect what the polkit-1 PAM configuration does for any other polkit action.
Uses sudo — no restart required
sudo tee /etc/polkit-1/rules.d/85-power-no-auth.rules << 'EOF'
polkit.addRule(function(action, subject) {
    var pmActions = [
        "org.freedesktop.login1.suspend",
        "org.freedesktop.login1.suspend-multiple-sessions",
        "org.freedesktop.login1.hibernate",
        "org.freedesktop.login1.hibernate-multiple-sessions",
        "org.xfce.power.xfce4-pm-helper"
    ];
    if (pmActions.indexOf(action.id) >= 0 && subject.isInGroup("fido-login")) {
        return polkit.Result.YES;
    }
});
EOF

Polkit watches /etc/polkit-1/rules.d/ and reloads automatically — no service restart needed.

What the rule does

ElementPurpose
pmActions arrayCovers both the logind suspend/hibernate actions and the XFCE power helper. The -multiple-sessions variants are included because logind uses them when more than one session is open, which is common on lab VMs.
subject.isInGroup("fido-login")Limits the grant to enrolled FIDO users. Non-members are not matched and fall through to the implicit policy unchanged.
polkit.Result.YESAuthorization granted without invoking PAM. The locked-screen authentication failure cannot occur because no PAM challenge is issued.

Verify

Test the full auto-suspend path: lock the screen with xflock4, then wait for the idle timeout configured in xfce4-power-manager. The system should suspend normally without a polkit prompt. To test immediately without waiting for the idle timer:

Normal user shell as FIDO_USER
systemctl suspend

Expected: system suspends without an authentication prompt. If a prompt appears, the rules file was not picked up — verify the path and filename:

Diagnostic
ls -l /etc/polkit-1/rules.d/

Rollback

Uses sudo
sudo rm -f /etc/polkit-1/rules.d/85-power-no-auth.rules

Polkit reloads immediately. Auto-suspend will fail again for fido-login members until the file is restored. To include this in a single emergency cleanup, add the rm command to the rollback block in section 21.

26. Fix NetworkManager authentication: polkit rules for network settings

After configuring polkit-1 PAM in section 24, modifying network settings through the XFCE NetworkManager applet will trigger a polkit authentication dialog that FIDO2 cannot satisfy — and the change will be blocked.

Why it fails

The NetworkManager action for modifying system-wide connection profiles has implicit active: auth_admin_keep. When a user edits a connection in the NM applet, NetworkManager requests this action and polkit invokes the graphical authentication agent. The agent drives the polkit-1 PAM stack, which now requires FIDO2. Because the agent cannot present a PIN field that maps to token hardware, authentication fails:

Actionimplicit activeimplicit inactive
org.freedesktop.NetworkManager.settings.modify.systemauth_admin_keepauth_admin

The dialog reads "System policy prevents modification of network settings for all users" with a password field the user cannot complete via FIDO2. Clicking Authenticate fails; clicking Cancel leaves the network change unapplied.

Confirm the failure action in the polkit journal:

Diagnostic
sudo journalctl -u polkit --since "5 minutes ago"

Expected output: a FAILED to authenticate entry for org.freedesktop.NetworkManager.settings.modify.system.

The fix: a polkit rules file

The same rules-file mechanism used in section 25 applies here. A rules file grants the NM action directly to fido-login group members, bypassing the PAM authentication challenge entirely.

Scope of this grant: the rule matches only org.freedesktop.NetworkManager.settings.modify.system and only for members of fido-login. It does not affect any other polkit action or any other user. Students outside the FIDO group are still subject to the normal auth_admin policy.
Uses sudo — no restart required
sudo tee /etc/polkit-1/rules.d/85-nm-no-auth.rules << 'EOF'
polkit.addRule(function(action, subject) {
    if (action.id === "org.freedesktop.NetworkManager.settings.modify.system" &&
        subject.isInGroup("fido-login")) {
        return polkit.Result.YES;
    }
});
EOF

Polkit watches /etc/polkit-1/rules.d/ and reloads automatically — no service restart needed.

What the rule does

ElementPurpose
action.id === "org.freedesktop.NetworkManager.settings.modify.system"Matches only the action that governs editing system-wide connection profiles — the one the NM applet requests when a user modifies a network connection.
subject.isInGroup("fido-login")Limits the grant to enrolled FIDO users, consistent with the power management rule in section 25. Non-members fall through to the normal auth_admin policy.
polkit.Result.YESAuthorization granted without invoking PAM. The graphical authentication dialog never appears.

Verify

As FIDO_USER, open the NetworkManager applet and edit an existing connection (for example, change the DNS server on the current connection and revert it). The change should apply without any polkit authentication dialog appearing.

To confirm via the journal that the action was granted rather than challenged:

Diagnostic
sudo journalctl -u polkit --since "2 minutes ago" | grep -i network

Expected: an Authorized action or Operator of unix-process entry for the NM action — no FAILED entry.

Rollback

Uses sudo
sudo rm -f /etc/polkit-1/rules.d/85-nm-no-auth.rules

Polkit reloads immediately. NetworkManager settings changes will again require polkit authentication, which will fail for FIDO users until the file is restored. To include this in a single emergency cleanup, add the rm command to the rollback block in section 21.

INSTRUCTOR ANSWER KEY

24-I. pkexec FIDO2 — step-by-step instructor procedures

Instructor note: this section is hidden until a student or instructor navigates directly to #show-instructor. Share the anchor URL only after the lab period ends, or use it for live demonstration. The section is not linked from the table of contents.

Step 1 — Confirm the current polkit-1 PAM file

Before any edit, students should read the existing file. On a stock Debian 13 XFCE install, /etc/pam.d/polkit-1 commonly contains only pam_permit.so, granting any user the right to authenticate polkit without any credential check:

Inspect current file
sudo cat /etc/pam.d/polkit-1

Common stock content:

#%PAM-1.0
auth       required    pam_permit.so
account    required    pam_permit.so
session    required    pam_permit.so
Teaching point: the pam_permit.so-only default is intentional on many distros because polkit's own .rules files are the primary authorization layer. However, for this lab's FIDO2 policy we are adding a real authentication layer on top of polkit's authorization check. Both layers are valid and complementary.

Step 2 — Back up and edit polkit-1

Uses sudo
sudo cp -a /etc/pam.d/polkit-1 /etc/pam.d/polkit-1.bak
sudo editor /etc/pam.d/polkit-1

Replace or augment the auth stack. The correct result:

Target polkit-1 content
#%PAM-1.0
# FIDO2 local authentication policy for pkexec/polkit.
# Users in fido-login authenticate with FIDO2 PIN + touch.
# Users not in fido-login fall through to common-auth (password).

@include fido2-local-auth

auth       required    pam_permit.so
account    required    pam_permit.so
session    required    pam_permit.so
Why place the include before pam_permit.so? The existing auth required pam_permit.so line would succeed unconditionally for any user. Placing @include fido2-local-auth first means FIDO users hit the FIDO check; if they succeed, pam_u2f returns success=done and the pam_permit.so line is never reached. Non-FIDO users skip the include (because they are not in fido-login) and land on pam_permit.so, which accepts them exactly as before. This is the minimal-impact insertion strategy.

Step 3 — Test with pamtester before triggering a real pkexec call

Uses sudo
sudo pamtester polkit-1 FIDO_USER authenticate

Expected: FIDO PIN prompt, then token flash prompt, then success.

sudo pamtester polkit-1 BREAKGLASS_USER authenticate

Expected: Unix password prompt only.

If FIDO_USER gets a password prompt instead of FIDO, the most common causes are:

  • The user is not in the fido-login group (run id FIDO_USER and getent group fido-login).
  • The fido2-local-auth include file has a syntax error.
  • The mapping file /etc/security/u2f_keys is missing or unreadable by the PAM process.

Step 4 — Test a real pkexec invocation from the FIDO user session

From a terminal running as FIDO_USER inside the graphical session:

Normal graphical session as FIDO_USER
pkexec /usr/bin/id

Two possible observed behaviors depending on whether a polkit agent is running:

Agent stateObserved behaviorCorrect?
No agent running (common on minimal XFCE)PAM conversation runs in the terminal. PIN prompt appears in the terminal. Token flashes. uid=0(root) output.Yes — correct and easiest to observe.
polkit-gnome agent runningA graphical dialog appears. It may show a PIN field or a password field depending on agent version. After PIN entry, token flashes. uid=0(root) output.Yes — if agent forwards the cue correctly.
polkit-gnome agent running, no PIN field visibleDialog shows a blank or generic password field. Student must enter the FIDO token PIN there (not the Unix password). Token flashes. Authentication succeeds.Functionally correct; graphical presentation is imperfect — same quirk as LightDM GTK greeter documented in section 12.

Step 5 — Negative tests

Wrong PIN test
sudo pamtester polkit-1 FIDO_USER authenticate

Enter an incorrect PIN deliberately. Expected: FIDO_ERR_PIN_INVALID, authentication failure, no password fallback.

No token present test
# Remove token first, then:
sudo pamtester polkit-1 FIDO_USER authenticate

Expected: pam_u2f fails to find the device, authentication fails. No password fallback offered.

Step 6 — Diagnosis guide for common failure modes

SymptomLikely causeDiagnostic command
pamtester shows password prompt for FIDO_USERUser not in fido-login group, or mapping file unreadableid FIDO_USER, sudo ls -l /etc/security/u2f_keys
pkexec prompts for password graphically instead of PINGraphical agent not forwarding the cue — this is cosmetic; entering the PIN in the password field still worksKill the agent with pkill polkit-gnome and retry in terminal for a cleaner test
pkexec says "Not authorized" even after FIDO succeedspolkit authorization (.rules file) denies the action — separate from authenticationpkaction --action-id <action> --verbose; check /usr/share/polkit-1/actions/
Authentication service cannot retrieve infomapping file permissions wrong or fido2-local-auth has a typosudo cat /etc/pam.d/fido2-local-auth, sudo cat /etc/pam.d/polkit-1
FIDO_ERR_NO_CREDENTIALS on a token with PINpamu2fcfg was run without -N; credential not registered for PINRe-run pamu2fcfg -N -u FIDO_USER and update /etc/security/u2f_keys

Step 7 — Instructor provision: block non-FIDO users from pkexec authentication

By default the configuration above allows users outside fido-login to skip the FIDO check and land on pam_permit.so, which grants them authentication unconditionally. In most lab deployments this is acceptable because polkit's own rules engine controls what actions each user may perform. However, if the policy goal is to ensure that only FIDO-enrolled users can authenticate through pkexec at all, replace the permissive fallback with an explicit deny.

Impact: after this change, BREAKGLASS_USER and any other non-FIDO account will receive an authentication failure from polkit-1 regardless of the action being requested. Confirm that your recovery path does not depend on pkexec before applying this. Console TTY and direct sudo remain unaffected.
Backup, then write the restricted polkit-1 file
sudo cp -a /etc/pam.d/polkit-1 /etc/pam.d/polkit-1.bak-pre-deny
sudo tee /etc/pam.d/polkit-1 >/dev/null <<'EOF'
#%PAM-1.0
# Restricted pkexec policy: only fido-login members may authenticate.
# Non-members receive an explicit auth failure -- pam_permit fallback removed.

# FIDO users: PIN + touch, then done.
auth [success=ignore default=1] pam_succeed_if.so quiet user ingroup fido-login
auth [success=done default=die] pam_u2f.so authfile=/etc/security/u2f_keys cue="FIDO users: enter token PIN, then touch token when it flashes" pinverification=1 userverification=0

# Non-FIDO users: explicit deny -- no fallback.
auth required pam_deny.so

account    required    pam_permit.so
session    required    pam_permit.so
EOF

The control-flow for each user class:

Userpam_succeed_ifpam_u2fpam_denyResult
FIDO_USER (in fido-login)success=ignore → continuesPIN + touch → success=doneNever reachedAuthenticated
FIDO_USER with wrong PINsuccess=ignore → continuesFails → default=dieNever reachedDenied
BREAKGLASS_USER (not in fido-login)default=1 → skips pam_u2fSkippedrequired → failsDenied

Verify the deny policy

Uses sudo
sudo pamtester polkit-1 FIDO_USER authenticate
sudo pamtester polkit-1 BREAKGLASS_USER authenticate

Expected: FIDO_USER succeeds with FIDO PIN + touch. BREAKGLASS_USER receives pamtester: Authentication failure immediately, with no password prompt offered.

Polkit rules still apply on top of this: even if PAM authentication succeeds for FIDO_USER, the specific action being requested must still be authorized by polkit's .rules files. This PAM change controls who can authenticate to polkit; the rules files control what authenticated users may do. Both layers remain active.

Rollback the deny policy (if needed)

Uses sudo
sudo cp -a /etc/pam.d/polkit-1.bak-pre-deny /etc/pam.d/polkit-1

Step 8 — Rollback the full pkexec lab (if needed)

Uses sudo
sudo cp -a /etc/pam.d/polkit-1.bak /etc/pam.d/polkit-1

This restores the stock pam_permit.so-only behavior. No service restart is required — PAM reads its configuration at authentication time. If the deny policy backup also exists, remove it:

sudo rm -f /etc/pam.d/polkit-1.bak-pre-deny

Extension challenge — answer

The stock pam_permit.so-only default exists because polkit already performs per-action authorization through its rules engine (/usr/share/polkit-1/actions/ and /etc/polkit-1/rules.d/). Adding an authentication layer via pam_u2f provides a hardware-backed, PIN-gated credential check on top of polkit's policy checks. In a production environment requiring strong authentication for privilege escalation, this layered approach is appropriate.

The group-gated pattern from fido2-local-auth ensures non-FIDO accounts (such as service accounts or the breakglass account) are not blocked by the hardware requirement, so the policy applies only to intentionally enrolled users.

Instructor note: wheel vs sudo group and polkit

wheel and sudo are functionally equivalent privilege-escalation conventions that come from different Unix lineages. Neither has any inherent special meaning to polkit — they are only meaningful if a .rules file explicitly checks for them.

GroupOriginDefault sudoers grantTypical distros
sudoDebian/Ubuntu convention%sudo  ALL=(ALL:ALL) ALLDebian, Ubuntu, Mint
wheelRed Hat/BSD lineage ("big wheel")%wheel ALL=(ALL:ALL) ALLFedora, RHEL, Arch, SUSE

On Debian, membership in sudo grants sudo access via /etc/sudoers — and nothing else. It has no effect on polkit unless a .rules file explicitly references it. The same is true of wheel.

Common polkit pitfall on Debian: polkit .rules files copied from upstream or Red Hat-oriented documentation often check for wheel. On a Debian system where no users are in wheel, those rules silently grant nothing, causing actions to be denied even when they appear to be permitted by policy. If a student reports unexpected polkit denials, check the relevant .rules file for a wheel reference and either add the user to wheel or edit the rule to reference sudo instead.

Both groups can coexist harmlessly. In lab environments it is common practice to add privileged users to both sudo and wheel to avoid ambiguity when rules files from mixed sources are in play. Neither group interacts with the FIDO2 PAM policy configured in this lab — that policy is gated on fido-login membership exclusively.

Summary of what students should take away: pkexec uses the polkit-1 PAM service. The same shared include file (fido2-local-auth) that gates LightDM and sudo can be inserted into polkit-1 with minimal change. The graphical agent layer introduces a UX quirk (the cue may not render correctly) but does not prevent correct FIDO authentication. Always test with pamtester before testing with a real graphical application.