Fix OIDC login immediate logout — rename JWT subject prefix ui: → user:
All checks were successful
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 41s
CI / deploy (push) Successful in 30s

OIDC tokens had subject "oidc:<sub>" which didn't match the "ui:" prefix
check in JwtAuthenticationFilter, causing every post-login API call to
return 401 and trigger automatic logout. Renamed the prefix from "ui:"
to "user:" across all auth code for clarity (it covers both browser and
API clients, not just UI).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-14 15:55:10 +01:00
parent 465f210aee
commit 6676e209c7
6 changed files with 16 additions and 16 deletions

View File

@@ -52,7 +52,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
JwtValidationResult result = jwtService.validateAccessToken(token);
String subject = result.subject();
if (subject.startsWith("ui:")) {
if (subject.startsWith("user:")) {
// UI user token — authenticate with roles from JWT
List<GrantedAuthority> authorities = toAuthorities(result.roles());
UsernamePasswordAuthenticationToken auth =

View File

@@ -110,7 +110,7 @@ public class OidcAuthController {
OidcTokenExchanger.OidcUserInfo oidcUser =
tokenExchanger.exchange(request.code(), request.redirectUri());
String userId = "oidc:" + oidcUser.subject();
String userId = "user:oidc:" + oidcUser.subject();
String issuerHost = URI.create(config.get().issuerUri()).getHost();
String provider = "oidc:" + issuerHost;
@@ -127,8 +127,8 @@ public class OidcAuthController {
userRepository.upsert(new UserInfo(
userId, provider, oidcUser.email(), oidcUser.name(), roles, Instant.now()));
String accessToken = jwtService.createAccessToken(userId, "ui", roles);
String refreshToken = jwtService.createRefreshToken(userId, "ui", roles);
String accessToken = jwtService.createAccessToken(userId, "user", roles);
String refreshToken = jwtService.createRefreshToken(userId, "user", roles);
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
} catch (ResponseStatusException e) {

View File

@@ -28,7 +28,7 @@ import java.util.List;
* Authentication endpoints for the UI (local credentials).
* <p>
* Validates credentials against environment-configured username/password,
* then issues JWTs with {@code ui:} prefixed subjects and ADMIN roles.
* then issues JWTs with {@code user:} prefixed subjects and ADMIN roles.
* Upserts the user into the user store on login.
*/
@RestController
@@ -70,7 +70,7 @@ public class UiAuthController {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials");
}
String subject = "ui:" + request.username();
String subject = "user:" + request.username();
List<String> roles = List.of("ADMIN");
// Upsert local user into store
@@ -81,8 +81,8 @@ public class UiAuthController {
log.warn("Failed to upsert local user to store (login continues): {}", e.getMessage());
}
String accessToken = jwtService.createAccessToken(subject, "ui", roles);
String refreshToken = jwtService.createRefreshToken(subject, "ui", roles);
String accessToken = jwtService.createAccessToken(subject, "user", roles);
String refreshToken = jwtService.createRefreshToken(subject, "user", roles);
log.info("UI user logged in: {}", request.username());
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
@@ -96,14 +96,14 @@ public class UiAuthController {
public ResponseEntity<AuthTokenResponse> refresh(@RequestBody RefreshRequest request) {
try {
JwtValidationResult result = jwtService.validateRefreshToken(request.refreshToken());
if (!result.subject().startsWith("ui:")) {
if (!result.subject().startsWith("user:")) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Not a UI token");
}
// Preserve roles from the refresh token
List<String> roles = result.roles();
String accessToken = jwtService.createAccessToken(result.subject(), "ui", roles);
String refreshToken = jwtService.createRefreshToken(result.subject(), "ui", roles);
String accessToken = jwtService.createAccessToken(result.subject(), "user", roles);
String refreshToken = jwtService.createRefreshToken(result.subject(), "user", roles);
return ResponseEntity.ok(new AuthTokenResponse(accessToken, refreshToken));
} catch (ResponseStatusException e) {