FAForever / FAForever/faf-user-service

Device verification page rejects the bare verification_uri (manual/cross-device login broken)

Open
#600 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Kotlin
Stars
3
Forks
6
Avg merge
2d 16h
Merged PRs (30d)
23

Description

## Summary

The device authorization verification page (`/oauth2/device-login`, `DeviceLoginView`) only works when opened via the `verification_uri_complete` returned by Hydra. Opening the bare `verification_uri` — as a user would when following the RFC 8628 manual flow ("go to this URL and enter this code") — fails with **"Zugriff verboten / Ungültiger Login-Weg"** (access forbidden / invalid login path).

## Steps to reproduce

1. Start an OAuth 2.0 Device Authorization Grant (RFC 8628) flow, e.g. a client `POST`s to `/oauth2/device/auth`.
2. Take the `verification_uri` from the response (e.g. `https://…/oauth2/device-login`, **without** query parameters) and open it manually in a browser.
3. The page renders an error instead of prompting for the user code.

Opening `verification_uri_complete` (which contains `device_challenge` and `user_code` as query params) works correctly.

## Root cause

`DeviceLoginView.beforeEnter` requires both `device_challenge` and `user_code` to be present as query parameters, otherwise it throws `NoChallengeException`:

```kotlin
override fun beforeEnter(event: BeforeEnterEvent?) {
val params = event?.location?.queryParameters?.parameters
val possibleChallenge = params?.get("device_challenge")?.firstOrNull()
val possibleUserCode = params?.get("user_code")?.firstOrNull()

if (possibleChallenge.isNullOrBlank() || possibleUserCode.isNullOrBlank()) {
throw NoChallengeException()
}
...
}
```

Since the bare `verification_uri` carries no query parameters, the manual path always throws.

## Why this matters

RFC 8628 §3.3 defines `verification_uri` as the end-user–facing URL the user is expected to visit and **then manually enter the `user_code`**. `verification_uri_complete` (§3.3.1) is only an optional convenience that pre-fills the code. A conforming device therefore may show the user just the `verification_uri` + `user_code` — this is the intended experience when the device cannot open a browser locally, or when the user completes authorization on a **different device** (phone, another computer).

Concretely: this came up in the FAF client (downlords-faf-client) device-login migration. Auto-opening `verification_uri_complete` works, but the displayed/clickable `verification_uri` fallback does not, so cross-device / headless authorization is currently impossible.

## Suggested fix

When `DeviceLoginView` is entered without a `device_challenge` (and/or `user_code`), instead of throwing, present an input field for the user to type their `user_code` and submit it, resolving the `device_challenge` through Hydra's device verification flow — then continue to the existing authorize step. The current behavior would remain the "fast path" when the complete URI is used.

## Environment

- Repo: `faf-user-service`
- Affected view: `src/main/kotlin/com/faforever/userservice/ui/view/oauth2/DeviceLoginView.kt`
- Hydra config: `hydra.yml` → `urls.device_verification: …/oauth2/device-login`

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.