Clearing stale device registration data disables local password auth, forcing MFA on every login and screen unlock
- Dominant language
- Go
- Stars
- 308
- Forks
- 41
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 58
Description
With `register_device = true`, a single Microsoft Graph group-fetch failure now permanently disables local password authentication for that user: every subsequent login *and every screen unlock* requires the full Entra password + MFA flow. This is a regression introduced by #1742.
Reported in https://github.com/canonical/authd/issues/1680#issuecomment-5281191510:
> while logged in to gnome, I locked the machine, came back to unlock it, and it still prompts me to use MFA, which is not ideal given how many times we lock/unlock a device per day - is this by design?
It is not by design.
## Root cause
Two pieces of logic interact badly:
1. `finishEntraAuth()` (`authd-oidc-brokers/internal/broker/broker.go:1843-1856`, added in #1742 / 18bb0d1ed) clears `authInfo.DeviceRegistrationData` when `getGroups()` fails with `RetryWithDeviceAuthError` (e.g. AADSTS7000218), so that the next login re-registers the device instead of reusing corrupt state. The cleared value is then persisted by `finishAuth()`.
2. `authModeIsAvailable()` (`broker.go:891-895`) treats "token carries device registration data" as the precondition for offering local password auth:
```go
isTokenForDeviceRegistration := dr.IsTokenForDeviceRegistration(authInfo)
if b.cfg.registerDevice && !isTokenForDeviceRegistration {
log.Noticef(..., "Token exists for user %q, but it cannot be used for device registration, so local password authentication is not available", ...)
return false
}
```
with `IsTokenForDeviceRegistration()` == `len(authInfo.DeviceRegistrationData) > 0` (`internal/providers/msentraid/msentraid.go:888-890`).
So step 1 guarantees the condition in step 2: local password auth is removed from the available auth modes, and `availableAuthModes()` falls through to the online modes only (`entra_password`, `device`, `device_qr`). GDM therefore presents the Entra password + MFA flow for every authentication, including screen unlock, and the user can never get back to the cheap local-password path while the Graph failure persists.
It is also self-sustaining: the same group fetch fails again on the next login (the underlying cause, e.g. an Entra app registration that is not a public client, has not changed), the field is cleared again, and so on.
## Expected behaviour
Clearing stale device registration data should force a *re-registration*, not degrade the user to MFA-on-every-unlock. Options to consider:
* Re-register the device within the same authentication instead of only clearing the cached data, so the persisted token ends up with valid registration data again.
* Distinguish "no device registered yet" from "registration data was invalidated" so that `authModeIsAvailable()` can keep offering local password auth while a re-registration is pending.
* At the very least, surface the situation to the user — the code path already carries a `// TODO: We might want to display a message to the user in this case` — instead of silently switching them to full MFA.
## Steps to reproduce
1. Configure `register_device = true` with an Entra app registration where the on-behalf-of Graph token exchange fails (e.g. AADSTS7000218 — see the parent report).
2. Log in with the Entra password + MFA flow. Observe `Could not get groups: ... Clearing stale device registration data and using cached groups.`
3. Lock the screen and unlock it.
4. Expected: local password prompt. Actual: full Entra password + MFA flow.
## Related
* #1742 (introduced the clearing)
* #1680 (parent report)
Contributor guide
Assessment
This issue has not been assessed yet.