Lab Metadata
| Attribute | Value |
|---|---|
| Lab ID | IB-SIA-05 |
| Track | Identity Bytes Senior IAM Architect Track (36 lab curriculum) |
| Difficulty | Intermediate (requires IB-SIA-01, IB-SIA-02, and IB-SIA-04) |
| Core technologies | Keycloak 26.0 authentication flows, TOTP (RFC 6238), WebAuthn / FIDO2, OpenLDAP (from IB-SIA-01), oathtool, a browser with a platform authenticator or virtual authenticator |
| Protocols and standards | TOTP (RFC 6238), HOTP (RFC 4226), WebAuthn Level 2 (W3C), FIDO2 CTAP2, NIST SP 800-63B authenticator assurance levels |
| Builds on | IB-SIA-04 (MFA attaches inside the authorization code flow's IdP login step) |
| Feeds into | IB-SIA-08 (adaptive and risk based authentication), IB-SIA-34 (FIDO2 and passkeys at depth), IB-SIA-12 (session controls) |
Lab Title and Description
From One Factor to None: Enforcing MFA and Then Going Passwordless with Passkeys
Everything so far has rested on a password. Lab 01 stored it, Lab 02 validated it through federation, Labs 03 and 04 moved the login to the IdP so that password reached only one hardened page. But a password entered on one hardened page is still a password: it can be phished, guessed, sprayed, and reused. This lab addresses that directly, in two moves. First, defence in depth: add a second factor so that stealing the password is no longer enough. Second, elimination: remove the password from the login entirely and replace it with a passkey, a credential that cannot be phished because it never leaves the user's device and refuses to work on the wrong website.
You will design a custom authentication flow in Keycloak, the same flow engine that runs the IdP login step of the Lab 04 authorization code flow, so every improvement here protects every client at once. You will enforce time based one time passwords (TOTP), generate the codes yourself from the command line with oathtool so the algorithm is not a mystery, then register a WebAuthn passkey and configure a passwordless flow. The centrepiece is a demonstration, not a claim: you will simulate the credential phishing that defeats passwords and even defeats TOTP, and then watch the passkey refuse to authenticate against the wrong origin, because origin binding is built into the protocol itself.
By the end you will have moved a user up the NIST SP 800-63B assurance ladder from a single memorised secret to a phishing resistant authenticator, and you will be able to explain, from your own captured evidence, exactly which attacks each rung stops. Estimated completion time is 3 to 3.5 hours.
Prerequisites
3.1 Prior labs required
| Lab | Why it is required |
|---|---|
IB-SIA-01 | OpenLDAP still stores the password that forms the first factor |
IB-SIA-02 | The northgate realm and its users are the subjects of every authentication policy here |
IB-SIA-04 | MFA is configured inside the authorization code flow's login step; you reuse app.py or the manual flow to see MFA in action from an application's point of view |
Restart and verify the environment
docker start ib-openldap ib-keycloak ib-phpldapadmin # Confirm the realm answers and asmith can still authenticate at the IdP curl -s http://localhost:8081/realms/northgate/.well-known/openid-configuration | jq -r '.issuer' # Expected: http://localhost:8081/realms/northgate
If Lab 04 left the hr-portal client on the authorization code flow with PKCE, you are in the ideal starting state. If not, it does not block this lab; the account console is used as the test application where needed.
3.2 System requirements
| Resource | Minimum | Recommended |
|---|---|---|
| Operating system | Ubuntu 22.04, macOS 13, or Windows 11 with WSL2 | Ubuntu 22.04 LTS |
| RAM | 6 GB | 8 GB |
| Browser | Chromium based browser strongly recommended: its developer tools include a Virtual Authenticator, which lets you complete WebAuthn without owning a physical key. Firefox works for TOTP but is harder for WebAuthn. | |
| Optional hardware | A real passkey (phone, laptop biometric, or a FIDO2 security key) if you want to feel the physical flow; not required, the virtual authenticator suffices. | |
3.3 Required tools and versions
| Tool | Version | Purpose |
|---|---|---|
| Docker, curl, jq | As installed in earlier labs | Container control and inspection |
| oathtool | 2.6 or later | Generates TOTP codes from the command line so you understand the algorithm rather than trusting an app |
| A TOTP app (optional) | Any | Google Authenticator, Aegis, or similar, if you prefer the phone experience alongside oathtool |
3.4 Installation commands
Ubuntu / Debian / WSL2 Ubuntu: install oathtool
# oathtool implements the same TOTP algorithm as any authenticator app. # Generating codes yourself demystifies the second factor. sudo apt-get update && sudo apt-get install -y oathtool
macOS 13+: install oathtool
brew install oath-toolkit
3.5 Verification of tools
Verify oathtool
# Generate a TOTP from a known test secret. The value changes every 30s. oathtool --totp -b "JBSWY3DPEHPK3PXP"
Expected: a six digit number. Run it twice thirty seconds apart and it changes. That is time based one time password generation, the whole of RFC 6238 in one command.
Real World Problem Statement
This lab solves the problem that every prior lab quietly deferred: the password is a single point of failure, and the most valuable one in the estate. Centralising authentication at the IdP concentrated risk as much as it concentrated control; the IdP login page now guards every federated application at once, so the strength of that single authentication event is the strength of everything. Passwords fail here in ways no amount of complexity policy fixes. They are phished at scale, sprayed against exposed IdPs, and reused from breach corpora. Verizon's breach reporting has for years put stolen or weak credentials at or near the top of initial access vectors, and the attacks that dominate now, adversary in the middle phishing, defeat not just passwords but also the one time codes many organisations added on top.
The answer arrives in two tiers. Multi factor authentication requires something beyond the password, so a stolen password alone is insufficient. But not all factors are equal: TOTP and push notifications are phishable, because a convincing fake site can relay the code or the approval in real time. The phishing resistant tier, WebAuthn and passkeys built on FIDO2, closes that gap by binding the credential cryptographically to both the user's device and the exact website origin, so the credential simply will not respond to an impostor domain. This is why regulators, cyber insurers, and NIST SP 800-63B now distinguish phishing resistant authenticators as a category of their own, and why a senior IAM architect must be able to design, enforce, and explain the whole ladder.
Why it matters, across four dimensions
Risk
A second factor removes the stolen password as a sufficient condition for compromise, and a phishing resistant factor removes real time credential relay, the technique behind the current wave of account takeovers.
Compliance
PCI DSS 4.0 mandates MFA for all access to the cardholder data environment, NIST SP 800-63B defines the assurance levels, and cyber insurance renewals increasingly require phishing resistant MFA for privileged access. This lab produces the enforcement evidence.
Productivity
Passkeys are faster than passwords, not slower: a biometric touch replaces typing and a second app. Passwordless reduces reset tickets, historically a large share of service desk volume, toward zero.
Security Posture
Phishing resistant authentication is a pillar of Zero Trust and a prerequisite for the step up and adaptive policies in IB-SIA-08. Strong authentication at the IdP is the highest leverage control in the entire identity estate.
Concrete scenario
Northgate Financial suffers a near miss: an adversary in the middle phishing campaign harvests an analyst's password and live TOTP code through a lookalike login page and briefly accesses an internal application before the session is spotted and killed. The board mandates phishing resistant MFA for all staff, with a phased path: enforce a second factor immediately for everyone, and move privileged and finance users to passwordless passkeys within the quarter. Your task in this lab is the reference implementation: a custom Keycloak authentication flow that requires a second factor, TOTP enrolment and enforcement, WebAuthn passkey registration, a passwordless flow for eligible users, and a documented demonstration that the passkey defeats the exact adversary in the middle attack that got through, providing the evidence the board's mandate requires.
Skills Mapped to Production Solutions
| Skill Learned | Real World Enterprise Application |
|---|---|
| Designing custom Keycloak authentication flows | Building the login journeys that every enterprise IdP rollout requires: conditional MFA, per group policies, and step up, in Keycloak, Okta, or Entra ID conditional access |
| Enforcing TOTP and understanding the algorithm | Rolling out app based MFA across a workforce, and troubleshooting the time drift and enrolment issues that generate real tickets |
| Registering and enforcing WebAuthn passkeys | Deploying phishing resistant authentication, the control regulators and insurers increasingly mandate for privileged access |
| Configuring a passwordless authentication flow | Leading passwordless programmes, one of the highest profile identity initiatives in enterprises today, with direct help desk cost impact |
| Mapping authenticators to NIST SP 800-63B AALs | Assurance level design and audit defence: justifying which authenticator satisfies which control to security review |
| Demonstrating why phishing resistance is categorical, not incremental | The architecture argument that wins budget for passkeys over cheaper TOTP, made from evidence rather than vendor slides |
| Configuring per group conditional MFA | Risk based policy: stronger requirements for finance and admin populations, the practical shape of least privilege in authentication |
Architecture Overview
Component breakdown
| Component | Purpose | Technology | Deployment | Key configuration |
|---|---|---|---|---|
| Custom browser flow | The ordered login journey: password, then a required second factor, with a conditional subflow for privileged groups | Keycloak authentication flow (cloned from browser) | Configured in the northgate realm | Bound as the realm browser flow; conditional OTP set to required; conditions on group membership |
| TOTP authenticator | Something you have: a rotating six digit code proving possession of an enrolled secret | RFC 6238 TOTP, generated by oathtool or an app | Per user credential in Keycloak | SHA-1, 6 digits, 30 second period (Keycloak default); enrolled via a required action |
| WebAuthn passkey | Phishing resistant factor and password replacement: a device bound key pair signed challenge, bound to the site origin | WebAuthn Level 2 / FIDO2 | Per user credential; verified by Keycloak's relying party | Passwordless policy configured under Authentication, WebAuthn Passwordless Policy; virtual authenticator used for testing |
| OpenLDAP | Still validates the first factor password | osixia/openldap (Lab 01) | Existing | Unchanged |
| Browser virtual authenticator | Lets you complete WebAuthn registration and login without physical hardware, and lets you fake a wrong origin | Chromium developer tools | Host browser | Enabled in the WebAuthn pane of developer tools |
Data flow (login under the custom flow)
- The client delegates to the IdP: as in Lab 04, the application sends the user to Keycloak's login, so the flow you build here protects every client automatically. Why: authentication policy belongs at the IdP, defined once and enforced everywhere.
- First factor, the password: Keycloak validates the password by LDAP bind. Why: this is the Lab 01 to Lab 02 foundation, now demoted from the whole of authentication to merely its first step.
- Conditional evaluation: the flow checks whether the user must present a second factor, always in the board's mandate, and applies stronger rules for members of privileged groups. Why: uniform enforcement with risk based escalation is how real policies are shaped.
- Second factor or passwordless: the user presents a TOTP code, or completes a WebAuthn assertion with a passkey; in the passwordless flow the passkey replaces the password step entirely. Why: the assurance level of the session is set here, and for passkeys the origin binding is enforced by the browser and the authenticator, not by Keycloak's good intentions.
- Tokens issued: only after the full flow succeeds does Keycloak complete the authorization code exchange from Lab 04 and issue tokens carrying the authentication context. Why: downstream applications can read how strongly the user authenticated and make their own risk decisions.
Security considerations
| Control | In this lab |
|---|---|
| Defence in depth | A stolen password alone no longer authenticates; a second factor is required and enforced by the flow, proven in Section 8 |
| Phishing resistance | WebAuthn binds the credential to the origin, so the captured wrong origin attempt is rejected by the authenticator before Keycloak is even involved |
| Assurance levels | Each factor combination maps to a NIST SP 800-63B AAL, documented so the choice is defensible to audit |
| Least privilege in authentication | Conditional subflows apply stronger requirements to finance and admin groups from Lab 01 |
| Recovery and lockout | Enrolment via required actions, and the interaction with brute force protection, are considered rather than left to chance |
Step by Step Implementation
Phase 1: Build a Custom Authentication Flow
Step 1.1: Clone the browser flow and require a second factor
Create an editable copy of Keycloak's default login journey and make the second factor mandatory rather than optional.
Keycloak ships a built in browser flow that you cannot edit directly, which is a safety feature. The professional pattern is to duplicate it, modify the copy, test it, and only then bind it as the realm's active flow. In the default flow the OTP step is conditional and effectively optional until a user enrols; the board's mandate requires it for everyone, so you will change that.
- Go to Authentication → Flows. Find browser, open its Action menu, choose Duplicate, name it northgate-mfa-browser.
- In the new flow, locate the sub step Browser - Conditional OTP (a subflow containing the OTP Form). Set the subflow requirement to Required so it always runs, and confirm the OTP Form inside it is Required.
- Return to Authentication → Flows, and on northgate-mfa-browser open the Action menu and choose Bind flow, binding it as the Browser flow.
Phase 2: Enrol and Enforce TOTP
Step 2.1: Trigger TOTP enrolment for a user
Require Amina to set up an authenticator on her next login, the enrolment path a real workforce rollout uses.
- Go to Users → asmith → Credentials. Under Required user actions, add Configure OTP. Save.
- Open http://localhost:8081/realms/northgate/account and sign in as asmith with the LDAP password.
- Keycloak now presents the OTP setup page with a QR code and, if you click "unable to scan", the base32 secret. Copy that secret string.
Generate the enrolment code with oathtool instead of a phone
# Paste the base32 secret Keycloak displayed. oathtool computes exactly # what any authenticator app would show for it, right now. SECRET="PASTE_THE_BASE32_SECRET_HERE" oathtool --totp -b "$SECRET" # Type the six digit result into the Keycloak setup page to complete # enrolment. If it rotates before you submit, run oathtool again.
Enrolment completes and the account console loads. Amina now has an OTP credential.
Step 2.2: Prove the second factor is enforced
Confirm that the password alone no longer grants access.
- Fully log out (or use a fresh private window). Go to the account console and sign in as asmith.
- After the password, Keycloak now demands a one time code. Generate it with
oathtool --totp -b "$SECRET"and enter it.
Phase 3: Register a WebAuthn Passkey
Step 3.1: Enable the virtual authenticator and register a passkey
Add a phishing resistant credential, using the browser's virtual authenticator so no hardware is required.
WebAuthn works by the authenticator generating a key pair per site, keeping the private key on the device, and signing a challenge from the relying party (Keycloak). Critically, the browser includes the exact origin in what gets signed, and the authenticator will only produce a signature for the origin it registered with. That origin binding, enforced below the application, is what makes passkeys phishing resistant. The Chromium virtual authenticator emulates a FIDO2 device entirely in software, which is ideal for learning and for the phishing demonstration in Section 8.
- In Chromium developer tools (F12), open the three dot menu, choose More tools → WebAuthn. Tick Enable virtual authenticator environment and add a new authenticator (protocol ctap2, transport internal, with resident keys and user verification supported).
- In the admin console, go to Users → asmith → Credentials → Set up credential is not the WebAuthn path; instead add the required action: Users → asmith → Required user actions → Webauthn Register Passwordless (for the passwordless credential) and Save. For a second factor style passkey, use Webauthn Register instead.
- In the private window with developer tools open, sign in as asmith (password plus TOTP), and Keycloak will prompt to register the passkey. Complete it; the virtual authenticator answers automatically.
Registration completes without hardware, and the virtual authenticator pane shows a stored credential.
Phase 4: Configure a Passwordless Flow
Step 4.1: Build and bind a passwordless browser flow
Let eligible users authenticate with a passkey alone, removing the password from the login entirely.
Passwordless does not mean less secure; it means removing the weakest link. A passkey is both something you have (the device) and, with user verification, something you are or know (biometric or PIN), so a single passkey step can meet AAL2 on its own. Keycloak models this with the WebAuthn Passwordless Authenticator, which you place where the password step used to be.
- Go to Authentication → Flows, duplicate browser again, name it northgate-passwordless.
- In the copy, remove or set to disabled the Password Form and the conditional OTP subflow, and add the execution WebAuthn Passwordless Authenticator, set to Required, after the username step. The intended journey is: identify the user, then authenticate with the passkey.
- Review the Authentication → Policies → WebAuthn Passwordless Policy: set User Verification Requirement to required so a biometric or PIN is always demanded, which is what lifts a single passkey to AAL2.
- Do not bind this as the realm default. Instead, test it by pointing a client at it or by using it as the account console flow temporarily, so only opted in users experience passwordless while the rest keep password plus MFA. In production this is where per group targeting lives.
A passwordless flow exists that authenticates asmith with only her passkey and user verification.
Phase 5: Per Group Conditional MFA
Step 5.1: Require stronger authentication for privileged groups
Apply the risk based half of the mandate: escalate requirements for the finance and admin groups you built in Lab 01.
Uniform MFA is the floor; real policy escalates by risk. Keycloak expresses this with a conditional subflow gated by a Condition - user role or group check: members of privileged groups traverse an extra or stronger step, everyone else takes the standard path. This is the Keycloak shape of the conditional access every enterprise runs, and it uses the very groups, finance-team and it-admins, that flowed from your Lab 01 LDIF through Lab 02 federation.
- In northgate-mfa-browser, add a subflow named privileged-step-up set to Conditional.
- Inside it add Condition - user role and configure it to match the it-admins role or group (map the group to a role if your Keycloak build conditions on roles), and add a required authenticator inside the subflow, for example WebAuthn, so admins must use the phishing resistant factor while general staff may still use TOTP.
- Save and test with two users: jpatel (it-admins) should be pushed toward the passkey, while a non privileged user is satisfied by TOTP.
Testing and Validation
The centrepiece: demonstrate why the passkey defeats the phishing that TOTP cannot
This is the evidence the board's mandate demanded. You will show, in your own environment, that a password and a TOTP code can both be relayed to a hostile origin, while a passkey cannot, because the authenticator refuses to sign for the wrong site.
Run the phishing resistance demonstration
# CONCEPT: adversary in the middle phishing works by hosting a lookalike # site that relays everything the user types to the real IdP in real time. # Passwords and TOTP codes are just strings, so they relay perfectly. # A WebAuthn assertion is signed over the ORIGIN the browser is on, so a # signature made at the fake origin is invalid at the real one. # We demonstrate the origin binding directly with the virtual authenticator. # STEP 1: In developer tools, WebAuthn pane, note that the registered # credential is bound to rpId matching localhost (the real IdP origin). # STEP 2: Simulate the wrong origin. Add a hosts entry so a different # name points at the same Keycloak, imitating a lookalike domain: echo "127.0.0.1 wrong-origin.local" | sudo tee -a /etc/hosts # STEP 3: Try the passwordless (passkey) login via the lookalike name: # http://wrong-origin.local:8081/realms/northgate/account # The browser will send origin http://wrong-origin.local:8081 in the # WebAuthn assertion. Keycloak's relying party ID expects localhost. # Expected: the WebAuthn step FAILS. The authenticator/browser will not # produce a usable assertion for a mismatched origin, and Keycloak # rejects it. The passkey cannot be phished to the wrong site. # STEP 4 (contrast): via the SAME wrong-origin name, the password + TOTP # login would proceed to collect both, and in a real relay attack those # captured strings would work against the genuine localhost IdP. The # difference is categorical, not a matter of degree. # CLEANUP the hosts entry afterwards: sudo sed -i '/wrong-origin.local/d' /etc/hosts
End to end scenario: the assurance ladder for one user
Walk asmith up the ladder
# RUNG 1: password only. Disable the MFA flow briefly (bind default # browser flow) and log in. Works with one factor. AAL1. Note it, revert. # RUNG 2: password + TOTP (northgate-mfa-browser bound). Log in; both # required. AAL2 with a memorised secret plus OTP device. # RUNG 3: password + passkey. Stronger: the second factor is now # phishing resistant. # RUNG 4: passkey only (northgate-passwordless). No password exists in # the login at all. With user verification required, this is AAL2 with a # phishing resistant authenticator and nothing to phish or reuse. # For each rung, note in your portfolio which attacks it stops, using # the factor ladder from the Section 6 diagram.
Negative and resilience tests
Run the tests
# TEST N1: wrong TOTP is refused (Phase 2.2 negative). Correct password, # wrong code: denied. # TEST N2: TOTP time drift. Generate a code, wait 90 seconds, then submit # the stale code. Expected: refused. TOTP codes are valid only within a # small window around their period (Keycloak default tolerates 1 step). # This is the drift issue behind real enrolment tickets when device # clocks are wrong. oathtool --totp -b "$SECRET" # note it, wait 90s, then try to submit it # TEST N3: lockout interaction. Enable brute force detection under # Realm settings, Security defenses, Brute force detection. Attempt # several wrong passwords for a user. Expected: temporary lockout, # independent of MFA. MFA and lockout are complementary controls. # TEST N4: recovery reality. In the admin console, delete asmith's OTP # and webauthn credentials, then attempt login. Expected: Keycloak forces # re-enrolment via the required action. This models the help desk path # when a user loses their authenticator, and why a fallback must exist # BEFORE passwordless enforcement.
Common failure modes and solutions
| Symptom | Likely cause | Solution |
|---|---|---|
| Locked out of the realm after binding the flow | The custom flow has a misconfigured required step with no valid path | Use the still authenticated admin tab, or the master realm admin, to re-bind the default browser flow, then fix the copy |
| TOTP code always rejected at enrolment | Host clock skew, or the secret was copied with a trailing space | Sync the host clock (NTP), re-copy the base32 secret cleanly, and submit promptly |
| WebAuthn registration does nothing | Virtual authenticator not enabled, or using Firefox where the pane differs | Use Chromium, enable the virtual authenticator with resident keys and user verification, retry |
| Passwordless flow still asks for a password | Password Form execution not disabled in the copied flow | Open the flow, set Password Form to disabled or remove it, keep only username plus WebAuthn Passwordless |
| Passkey login fails on the real origin too | Relying party ID mismatch between registration and login, or WebAuthn policy RP ID set wrong | Ensure the WebAuthn policy Relying Party ID matches the host used for both registration and login (localhost) |
| Conditional step up never triggers | Condition checks a role but the group is not mapped to a role | Map the group to a realm role, or use a group based condition supported by your Keycloak version |
Security Analysis
What makes this implementation secure
- A stolen password is no longer sufficient: the custom flow enforces a second factor for all users, proven by the wrong code denial and the enforced login.
- The phishing resistant tier is genuinely categorical: the WebAuthn assertion is bound to the origin, so a credential registered for the real IdP cannot be exercised against a lookalike, which the wrong origin test demonstrated.
- Passwordless removes the phishable secret entirely: with user verification required, a single passkey step meets AAL2 while leaving nothing to phish, spray, or reuse.
- Risk based escalation applies stronger authentication to privileged groups, carrying the Lab 01 authorisation model into authentication strength.
- Each factor combination maps to a documented NIST SP 800-63B assurance level, so the design is defensible to audit rather than asserted.
- MFA and brute force lockout were shown to be complementary, and recovery was tested rather than assumed.
What is intentionally simplified for the lab
- Everything runs over HTTP on localhost; production WebAuthn requires HTTPS (browsers permit localhost as a special case), and the relying party ID must be the real domain.
- The phishing demonstration uses one Keycloak behind two names to show origin binding, not a full two server relay proxy.
- Account recovery and fallback authenticators are discussed and tested minimally, not designed to production depth.
- TOTP uses Keycloak defaults (SHA-1, 6 digits, 30s, 1 step drift) rather than a tuned policy.
- The passwordless flow is opt in for testing rather than staged across a real population with a pilot and comms plan.
Production hardening recommendations
| Recommendation | Why | Covered in |
|---|---|---|
| Enforce HTTPS with the real domain as the WebAuthn relying party ID | WebAuthn origin binding only protects the true origin; misconfigured RP ID silently weakens it | IB-SIA-17 to 19 (PKI phase) |
| Prioritise phishing resistant authenticators for privileged and remote access | Adversary in the middle phishing defeats passwords and OTP; only origin bound credentials stop it, as demonstrated | This lab, extended in IB-SIA-34 |
| Design account recovery before enforcing passwordless: multiple passkeys, a vetted help desk path, and backup authenticators | A user with one lost passkey and no fallback is locked out; recovery is the hard part of passwordless | IB-SIA-33 (identity incident response) |
| Layer adaptive, risk based conditions: device, location, impossible travel, behaviour | Static per group rules are the floor; real conditional access reacts to signals in real time | IB-SIA-08 (adaptive authentication) |
| Tune brute force and account lockout to balance security and denial of service risk | Aggressive lockout is itself a DoS vector; the settings need a threat model | IB-SIA-08 |
| Emit and monitor authentication context (AMR, ACR) so applications can enforce step up | Downstream apps should be able to demand a higher assurance for sensitive actions | IB-SIA-12 (session controls) |
| Alert on MFA enrolment changes, factor downgrades, and passkey registration events | Attackers who gain a session often add their own authenticator; these events are high signal | IB-SIA-35 (identity observability) |
Cleanup Instructions
Option A: pause the lab, keep everything (recommended)
Stop containers
docker stop ib-keycloak ib-openldap ib-phpldapadmin
docker start ib-openldap ib-keycloak ib-phpldapadmin # to resume
Option B: revert authentication changes cleanly
Return the realm to a known good login and remove test artefacts
# In the admin console (realm northgate): # Authentication, Flows: bind the default "browser" flow again if you # want to remove MFA enforcement, or keep northgate-mfa-browser bound # (recommended: MFA is a good default to carry into later labs). # Users, asmith, Credentials: remove test otp/webauthn credentials if # you want a clean slate for re-running the lab. # Remove the hosts entry if the phishing demo left one behind: sudo sed -i '/wrong-origin.local/d' /etc/hosts # Disable the virtual authenticator in developer tools when finished.
Recommended Learning Links
- NIST SP 800-63B: Digital Identity Guidelines, Authentication and Lifecycle Management (the AAL definitions and phishing resistance)
- RFC 6238: TOTP, Time Based One Time Password Algorithm
- RFC 4226: HOTP, HMAC Based One Time Password Algorithm
- W3C Web Authentication (WebAuthn) Level 2
- FIDO Alliance specifications (CTAP2, passkeys)
- Keycloak Server Administration Guide: WebAuthn
- Keycloak Server Administration Guide: Authentication flows
- Passkeys overview (FIDO Alliance)
Portfolio Publishing Guide: Evidence of a Phishing Resistant Design
Lab 05 is the most quotable lab yet for a portfolio, because it ends in a demonstration with a clear, board level takeaway: passkeys stop the phishing that passwords and codes do not. The assets are your custom flow, the assurance ladder mapping, and the phishing resistance write up. None of it contains secrets that need heavy redaction, but authenticator details still deserve care.
12.1 Collect the artefacts
Export the flow and assemble evidence
cd ~/identity-bytes-architect-labs
mkdir -p lab-05-mfa-passwordless/{docs,config,evidence,screenshots}
# Export the northgate realm again; the custom flows travel in it.
docker exec ib-keycloak /opt/keycloak/bin/kc.sh export \
--dir /opt/keycloak/data/export --realm northgate --users skip
docker cp ib-keycloak:/opt/keycloak/data/export/northgate-realm.json \
./northgate-realm.json
# Sanitise (blank any client secrets) before publishing, as in Lab 02:
jq '(.clients[]? | select(has("secret")) | .secret) = "REDACTED"' \
northgate-realm.json > lab-05-mfa-passwordless/config/northgate-realm.sanitised.json
rm northgate-realm.json
# Write the assurance ladder as a short markdown table for evidence.
cat > lab-05-mfa-passwordless/evidence/assurance-ladder.md << 'EOF'
# Authenticator assurance ladder (as configured and tested)
| Rung | Factors | Stops | NIST AAL (approx) |
|------|---------|-------|-------------------|
| 1 | Password only | Nothing modern (phish, spray, reuse) | AAL1 |
| 2 | Password + TOTP | Reuse, spraying | AAL2 (not phish resistant) |
| 3 | Password + Passkey | Also real-time phishing | AAL2 |
| 4 | Passkey only (UV required) | Nothing to phish or reuse | AAL2, phishing resistant |
Demonstrated: a passkey registered for the real origin could not be
exercised against a lookalike origin, because the WebAuthn assertion is
origin-bound. Password and TOTP, being strings, relay to any origin.
EOF
12.2 Add Lab 05 to the repository and push
Create the README, commit, and push
cat > lab-05-mfa-passwordless/README.md << 'EOF' # Lab 05: Multi Factor and Passwordless Authentication Part of my Identity Bytes Senior IAM Architect lab series (IB-SIA-05). Hardens the IdP login step that protects every client from Lab 04, moving one user from a single password up to a phishing resistant passkey. ## Problem this solves Centralising authentication concentrated risk on one login event. A stolen password there compromises everything, and the current wave of adversary in the middle phishing defeats not just passwords but also TOTP. The fix is a second factor, and then a phishing resistant one. ## What I built - A custom Keycloak authentication flow (cloned, edited, bound) enforcing a second factor for all users - TOTP enrolment and enforcement, with codes generated from the command line via oathtool to show the RFC 6238 algorithm - WebAuthn passkey registration using the browser virtual authenticator - A passwordless flow authenticating with a passkey alone (user verification required) - Per group conditional step up reusing the Lab 01 finance/admin groups ## The demonstration Showed that a passkey registered for the real origin cannot be used against a lookalike origin, because the WebAuthn assertion is origin bound, while a password and TOTP code relay to any origin. This is why passkeys are phishing resistant and TOTP is not: a categorical difference, not an incremental one. ## Assurance ladder Mapped each factor combination to a NIST SP 800-63B AAL. See evidence/assurance-ladder.md. ## Skills demonstrated Authentication flow design, TOTP and WebAuthn/FIDO2, passwordless, conditional/risk based MFA, and assurance level justification. Full guide: docs/IB-SIA-05-mfa-passwordless.html EOF cp <PATH_TO>/IB-SIA-05-mfa-passwordless.html lab-05-mfa-passwordless/docs/ git add . git commit -m "Lab 05: custom MFA flow, TOTP, WebAuthn passkeys, passwordless, phishing resistance demo" git push # Track index: | 05 | MFA, TOTP, WebAuthn passkeys, passwordless | Complete |
12.3 Share it on LinkedIn
Attach the factor ladder from the Section 6 diagram, or a screenshot of the passwordless login completing with no password field, because a passwordless login is a striking image for a general audience. GitHub link in the first comment. A draft in the Identity Bytes style:
Draft post:
I tried to phish my own passkey this weekend. It refused to be phished, and it did not need my cleverness or vigilance to refuse. It simply could not do the wrong thing.
Here is why that matters more than it sounds. For years the advice has been add multi factor authentication, and it was good advice, but the attackers adapted. The dominant technique now, adversary in the middle phishing, stands up a lookalike login page that relays everything you type to the real site in real time. Your password relays. Your six digit code relays. Both arrive at the genuine site and both work, because they are just strings, and a string does not know which website it was typed into.
A passkey is not a string. When you authenticate, your device signs a challenge, and the browser folds the exact website address into what gets signed. A passkey registered for your real bank will not produce a valid signature for a lookalike domain, because the address is wrong, and the device checks. I demonstrated exactly this: a passkey that worked perfectly on the real origin was inert against an impostor origin. The password and the code, on the same test, would have sailed straight through to the attacker.
So this week I built the whole ladder in Keycloak, from a single password up to a passwordless passkey login with nothing to steal, and mapped each rung to the assurance levels an auditor recognises. The most important rung is the one where phishing resistance stops being a matter of user awareness and becomes a property of the credential itself.
The lesson I keep coming back to: the strongest security controls are the ones that do not depend on a human noticing something is wrong. Awareness training asks people to be perfect. Origin binding makes the attack impossible.
Lab five of thirty six in the senior IAM architecture series I am publishing. Guide, the sanitised flow export, and the assurance ladder are on my GitHub, link in the comments.
For those still standing up MFA: is your second factor one an attacker can relay in real time, or one they physically cannot?
Same cadence: Tuesday to Thursday morning UK time, replies within two hours, hashtags at the end (#IAM #MFA #Passkeys #FIDO2 #CyberSecurity), and a comment linking the earlier labs so the series compounds.
12.4 Interview leverage from this lab
This lab evidences the identity security baseline lines of the target role, MFA, passwordless, passkeys, mTLS adjacent thinking, and Zero Trust, and it arms you for the question that separates senior from mid level candidates: not "do you use MFA" but "which MFA, and why". You can answer that a push or OTP factor is phishable and a FIDO2 passkey is not, and then explain the mechanism, origin binding, from a demonstration you ran. When the follow up is about rolling passwordless out safely, you can talk about staged enrolment, recovery design, and per group targeting because you built the conditional flow that does it.