Multi-Factor Authentication#
Multi-Factor Authentication (MFA) works with ssh-oidc end to end. There are two separate questions, and they are easy to confuse:
- Can the resource see that MFA was used? Yes – Motley Cue reads the MFA signal from the token and can act on it.
- How often does the user have to produce a second factor? This is the
interesting part: with
oidc-agent, once per refresh-token validity period – not once per login.
Where the MFA signal comes from#
Providers express “a second factor was used” in different places:
- the REFEDS MFA profile
https://refeds.org/profile/mfa, usually ineduperson_assurance - the authentication context class
acr, sometimes as a bare value such as2 - the authentication methods
amr, e.g.mfa
Motley Cue collects the configured claims from the userinfo endpoint, the access token body (if it is a JWT) and token introspection, and evaluates the union of what it finds. Which claims are inspected, and how expressions over them are written, is described under assurance-based shell tiers.
REFEDS MFA is not under the assurance prefix
The MFA profile is https://refeds.org/profile/mfa, while the REFEDS
Assurance Framework lives under https://refeds.org/assurance/. Always
write MFA as the absolute URL in your expressions – details and the
failure mode are
here.
The typical use is to require MFA for the full shell tier, and hand users
without it a restricted shell instead of rejecting them.
How often the user authenticates#
This is where federated SSH differs from password-based SSH with an OTP.
With oidc-agent, the second factor is produced when the refresh token
is obtained, and everything downstream is derived from it without user
interaction:
| Credential | How it is obtained | Typical lifetime | Second factor? |
|---|---|---|---|
| Refresh token | oidc-gen, interactive login at the OP |
weeks to months | yes – here |
| Access token | derived from the refresh token by oidc-agent |
minutes to hours | no |
| ssh-certificate | issued by oinit-ca for an access token |
set by the site (cert-validity) |
no |
So a user running oidc-gen once per month authenticates with their second
factor once per month, and logs in via ssh arbitrarily often in between.
Compared to typing a one-time password at every ssh invocation, this is
both stronger (the second factor is required, and a real OP login flow
rather than a shared secret) and less work.
This applies to the oidc-agent based clients
oinit and
mccli obtain their tokens from
oidc-agent, so the above holds for them.
web-ssh runs an authorization code flow in the
browser instead and does not use a refresh token. How often a second
factor is requested there is governed by the OP’s own web session, not
by ssh-oidc.
Users set this up in step 3 of the
client installation, with oidc-gen.
What this does and does not guarantee#
The OP must carry the signal into derived tokens
Whether acr / amr still reflects the original MFA event on tokens
minted from a refresh token is up to the OP. Some providers
propagate the authentication context, some do not.
Where they do not, an expression such as
assurance_based_shell_tier_full = https://refeds.org/profile/mfa
stops matching after the first access token expires, and users silently
fall through to assurance_based_shell_default_tier – exactly the
logins the policy was meant to reward get downgraded. Verify this per
OP before relying on it.
MFA is bound to refresh-token issuance, not to the session
The flip side of the convenience: the second factor is tied to the
refresh token, so refresh-token validity is the security parameter
that matters. Shortening cert-validity in
oinit-ca forces
certificates to be renewed more often, but it does not force a fresh
second factor – renewal happens from the refresh token, without the
user.
ssh-oidc cannot compel a fresh second factor per session. If you need
that, it has to be enforced on the OP side (e.g. via acr_values or
IdP session policy) and by limiting refresh-token lifetime.
Checking what arrives#
Set log_level = DEBUG in the [mapper] section of
/etc/motley_cue/motley_cue.conf. Motley Cue then logs, per request, every
configured claim and the values found for it per source, followed by the
tier that was selected. That shows directly whether the MFA signal is
present at all, and whether it survives a token refresh. See the
troubleshooting notes for
details.
Last change: Nov 12, 2026 10:48:39 by Marcus Hardt [branch: HEAD (7a06646)]