canonical / canonical/authd

Clearing stale device registration data disables local password auth, forcing MFA on every login and screen unlock

Open
#1,790 0 comments 0 reactions 1 assignee Claimed by @nooreldeenmansour View on GitHub
jira
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.