Skip to content

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:

  1. Can the resource see that MFA was used? Yes – Motley Cue reads the MFA signal from the token and can act on it.
  2. 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 in eduperson_assurance
  • the authentication context class acr, sometimes as a bare value such as 2
  • 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)]