feature(middleware): JWT refresh token#4492
Conversation
…o newline at end of file
Refactored AuthenticationTokenController to:
- Rename TokenPair endpoints for clarity ([HttpPost("Get")] → [HttpPost("GetTokenPair")], [HttpPost("GetForUser")] → [HttpPost("GetTokenPairForUser")).
- Add new endpoints to return only JWT strings: [HttpPost("Get")] and [HttpPost("GetForUser")].
- Provide XML documentation for new endpoints.
…essToken(), updating all usages accordingly. Improved TokenService initialization to robustly handle exceptions when reading from session storage, clearing invalid tokens and logging warnings as needed. Extracted token clearing logic to a new private method. Restructured Login.razor form markup for clarity and updated session restoration logic to use the new API and handle errors gracefully. Updated MainLayout.razor to use the new method names. These changes enhance code clarity, error handling, and maintainability.
Added [Authorize] to RefreshToken and RevokeToken endpoints, requiring a valid JWT access token. Changed RefreshToken to return BadRequest (400) for invalid/expired refresh tokens. Introduced a DEBUG-only TestAuth endpoint for testing JWT authentication via Swagger. Enhanced Swagger/OpenAPI docs to include JWT Bearer authentication and security requirements.
Strengthen refresh token rotation by ensuring each refresh token can only be used once. The RevokeRefreshToken method now returns the number of affected rows, and token refresh endpoints verify that exactly one token is revoked before issuing new tokens. Add logging for replay attempts and an integration test to confirm that only one of multiple concurrent refresh requests with the same token succeeds. This mitigates replay attacks and improves overall authentication security.
Refactor user context handling for authentication and token refresh. - Rename and generalize HandleUiUserAtLogin to SynchronizeUiUserContext - Prevent login state updates during refresh token operations. - Simplify JWT creation
Replaces RequiresGitHubActionsAttribute with RequiresIntegrationEnvironmentAttribute to allow integration tests to run locally or in CI when FWO_RUN_INTEGRATION_TESTS=true. AuthenticationTokenIntegrationTest now reads credentials from environment variables, skipping tests with a clear message if not configured. Updates GitHub Actions workflow to set integration test variables and report test mode. Adds runsettings files for integration test configuration and updates .gitignore. Tests now skip gracefully if credentials are invalid. Improves safety and flexibility for running JWT integration tests.
…-orchestrator into jwt_token_lifetime_internal
…-orchestrator into jwt_token_lifetime_internal
…-orchestrator into jwt_token_lifetime_internal
There was a problem hiding this comment.
Pull request overview
Implements JWT refresh-token support across the middleware, UI, and supporting automation, including configurable token lifetimes and additional tests/docs to validate and run the new flow.
Changes:
- Introduces access+refresh token pair endpoints (issue/refresh/revoke), refresh-token persistence, and shorter internal-service JWT lifetimes with rotation.
- Updates UI authentication/session restoration to store token pairs and periodically refresh expired access tokens.
- Adds integration test opt-in via
.runsettings, CI wiring, and updated help/docs/examples.
Reviewed changes
Copilot reviewed 90 out of 92 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| roles/ui/files/FWO.UI/wwwroot/css/site.css | Formatting + new help text styles |
| roles/ui/files/FWO.UI/wwwroot/css/help-code.css | New CSS for code blocks in help pages |
| roles/ui/files/FWO.UI/Shared/MonitoringLayout.razor | Adds (Debug-only) nav link to JWT token page |
| roles/ui/files/FWO.UI/Shared/MainLayout.razor | Adds periodic access-token refresh checks + event handling updates |
| roles/ui/files/FWO.UI/Services/TokenService.cs | New session token-pair storage + refresh/revoke logic |
| roles/ui/files/FWO.UI/Services/SessionStorageWrapper.cs | Wrapper to abstract protected session storage |
| roles/ui/files/FWO.UI/Services/PeriodicTaskRunner.cs | Utility for periodic async callbacks |
| roles/ui/files/FWO.UI/Services/JwtEventService.cs | Removes old static JWT timer/event service |
| roles/ui/files/FWO.UI/Services/ISessionStorage.cs | New interface for session storage abstraction |
| roles/ui/files/FWO.UI/Program.cs | Registers TokenService + storage wrapper; uses token pair bootstrap |
| roles/ui/files/FWO.UI/Pages/Settings/SettingsRoles.razor | Publishes permission-change events via mediator |
| roles/ui/files/FWO.UI/Pages/Settings/SettingsDefaults.razor | Adds configurable access/refresh token lifetimes with validation |
| roles/ui/files/FWO.UI/Pages/Monitoring/MonitorJWTToken.razor | New (Debug-only) admin token view/refresh/revoke page |
| roles/ui/files/FWO.UI/Pages/Logout.razor | Routes logout through async deauthentication cleanup |
| roles/ui/files/FWO.UI/Pages/Login.razor | Restores auth via token-pair restore flow; uses token pair auth |
| roles/ui/files/FWO.UI/Pages/Help/HelpSettingsDefaults.cshtml | Documents new token lifetime settings |
| roles/ui/files/FWO.UI/Pages/Help/HelpLayout.cshtml | Includes new help-code stylesheet |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiSubnetDataImport.cshtml | Uses formatted code blocks |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiReporting.cshtml | Updates API examples to token-pair endpoint + formatted blocks |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiLogin.cshtml | Documents token-pair issuance/refresh/revoke examples |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiFwoQuery.cshtml | Updates sample queries + formatting |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiFwoMutation.cshtml | Updates sample mutation + formatting |
| roles/ui/files/FWO.UI/Pages/Help/HelpApiAppDataImport.cshtml | Reformats JSON sample in help |
| roles/ui/files/FWO.UI/Auth/AuthStateProvider.cs | Token-pair aware auth, restore, refresh, revoke-on-logout |
| roles/tests-unit/files/FWO.Test/UiUserHandlerTest.cs | Unit tests for token lifetime lookup defaults |
| roles/tests-unit/files/FWO.Test/TokenLifetimeProviderTest.cs | Unit tests for lifetime provider behaviors |
| roles/tests-unit/files/FWO.Test/SimulatedUserConfig.cs | Makes GetText more resilient in tests |
| roles/tests-unit/files/FWO.Test/PeriodicTaskRunnerTest.cs | Unit test for periodic task runner |
| roles/tests-unit/files/FWO.Test/Mocks/MockProtectedSessionStorage.cs | Test storage mock implementing ISessionStorage |
| roles/tests-unit/files/FWO.Test/Mocks/MockMiddlewareClient.cs | Middleware client mock for refresh/revoke calls |
| roles/tests-unit/files/FWO.Test/LockTest.cs | Adjusts parallelization settings |
| roles/tests-unit/files/FWO.Test/JwtWriterClaimsTest.cs | Adds test for jti claim |
| roles/tests-unit/files/FWO.Test/InternalApiTokenServiceTest.cs | Tests internal API token rotation/refresh |
| roles/tests-unit/files/FWO.Test/Helpers/RequiresIntegrationEnvironmentAttribute.cs | Opt-in attribute for integration-style tests |
| roles/tests-unit/files/FWO.Test/FWO.Test.csproj | Adds MVC testing package for integration test harness |
| roles/tests-unit/files/FWO.Test/DataGenerators/TokenTestDataBuilder.cs | Builder for auth test data |
| roles/tests-unit/files/FWO.Test/DataGenerators/AuthTestHelpers.cs | Common assertions for token workflows |
| roles/tests-unit/files/FWO.Test/ConfigFileTest.cs | Updates expected exception types |
| roles/tests-unit/files/FWO.Test/AuthenticationTokenIntegrationTest.cs | Adds end-to-end integration tests for refresh/revoke flows |
| roles/tests-unit/files/FWO.Test/AuthenticationTokenControllerAuditTest.cs | Tests audit-text content for issued tokens |
| roles/tests-unit/files/FWO.Test/AuthStateProviderTest.cs | Unit tests for restore/refresh/auth-state behavior |
| roles/tests-integration/tasks/test-auth.yml | Updates Ansible integration test to token-pair endpoint |
| roles/tests-integration/handlers/main.yml | Adds LDAP cleanup handler for integration test user |
| roles/sample-auth-data/templates/tree_roles_for_integration_testuser.ldif.j2 | Grants reporter role to integration test user |
| roles/sample-auth-data/templates/tree_integration_testuser_jwt.ldif.j2 | Creates integration test user entry |
| roles/sample-auth-data/tasks/modify_ldap_tree.yml | Ensures integration test user + role are provisioned |
| roles/middleware/files/FWO.Middleware.Server/UiUserHandler.cs | Adds lifetime-key-based config lookup + refactors user sync |
| roles/middleware/files/FWO.Middleware.Server/Services/TokenLifetimeProvider.cs | Central token lifetime provider |
| roles/middleware/files/FWO.Middleware.Server/Services/InternalApiTokenServiceOptions.cs | Options for internal token refresh timing |
| roles/middleware/files/FWO.Middleware.Server/Services/InternalApiTokenService.cs | Short-lived internal JWT rotation logic |
| roles/middleware/files/FWO.Middleware.Server/Services/InternalApiTokenRefreshService.cs | Background hosted service for token rotation |
| roles/middleware/files/FWO.Middleware.Server/RecertCheck.cs | Uses shorter reporter token lifetime |
| roles/middleware/files/FWO.Middleware.Server/Program.cs | Configures JSON naming policy + registers token refresh services + swagger security |
| roles/middleware/files/FWO.Middleware.Server/JwtWriter.cs | Adds jti, removes ultra-long token lifetimes, adds refresh-token generator |
| roles/middleware/files/FWO.Middleware.Server/Jobs/ReportJob.cs | Uses capped delegated token lifetime |
| roles/middleware/files/FWO.Middleware.Server/Jobs/DailyCheckJob.cs | Passes lifetime provider into recert check |
| roles/lib/files/FWO.Services/EventMediator/Events/PermissionChangedEventArgs.cs | New event args type |
| roles/lib/files/FWO.Services/EventMediator/Events/PermissionChangedEvent.cs | New event type |
| roles/lib/files/FWO.Services/EventMediator/Events/JwtExpiredEventArgs.cs | New event args type |
| roles/lib/files/FWO.Services/EventMediator/Events/JwtExpiredEvent.cs | New event type |
| roles/lib/files/FWO.Middleware.Client/MiddlewareClient.cs | Switches auth to token-pair endpoint + adds refresh/revoke client calls |
| roles/lib/files/FWO.Data/Middleware/TokenPair.cs | New DTO for access/refresh tokens |
| roles/lib/files/FWO.Data/Middleware/RefreshTokenRequest.cs | DTO for refresh/revoke requests |
| roles/lib/files/FWO.Data/Middleware/RefreshTokenInfo.cs | DTO for refresh-token DB records |
| roles/lib/files/FWO.Config.File/ConfigFile.cs | Supports env-var overrides for config/key paths + exception changes |
| roles/lib/files/FWO.Config.Api/UserConfig.cs | Improves API error-code parsing |
| roles/lib/files/FWO.Config.Api/Data/ConfigData.cs | Adds access/refresh lifetime config fields |
| roles/lib/files/FWO.Api.Client/Queries/AuthQueries.cs | Adds refresh-token GraphQL queries |
| roles/lib/files/FWO.Api.Client/GraphQlApiConnection.cs | Removes placeholder expired-JWT special-case |
| roles/importer/files/importer/import_mgm.py | Parses access token from token-pair response |
| roles/importer/files/importer/fwo_api.py | Updates login endpoint to token-pair endpoint |
| roles/database/tasks/install-database.yml | Adds refresh-token table creation to new installs |
| roles/database/files/upgrade/9.1.0.sql | Upgrade script for refresh_token table |
| roles/database/files/sql/idempotent/fworch-texts.sql | Adds new UI/help texts for token features |
| roles/database/files/sql/creation/fworch-create-tables-jwt-refresh.sql | Refresh token table creation script |
| roles/common/files/fwo-api-calls/auth/storeRefreshToken.graphql | Mutation to persist refresh token hash |
| roles/common/files/fwo-api-calls/auth/revokeRefreshToken.graphql | Mutation to revoke refresh token |
| roles/common/files/fwo-api-calls/auth/getRefreshToken.graphql | Query to validate refresh token |
| roles/api/files/replace_metadata.json | Adds Hasura permissions for refresh_token table |
| inventory/group_vars/testservers.yml | Adds integration test user config for test servers |
| inventory/group_vars/all.yml | Bumps product version to 9.1.0 |
| FWO.local.runsettings.example | Example local runsettings enabling integration tests |
| FWO.default.runsettings | Default runsettings disabling integration tests |
| CONTRIBUTING.md | Documents runsettings-based test execution |
| .vscode/tasks.json | Adds VS Code tasks for default vs local JWT integration tests |
| .gitignore | Ignores local runsettings file |
| .github/workflows/test-install.yml | Runs JWT integration tests + adjusts Ansible steps |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-orchestrator into jwt_token_lifetime_internal
…-orchestrator into jwt_token_lifetime_internal
…-orchestrator into jwt_token_lifetime_internal
|
| @if (IsDebugBuild) | ||
| { | ||
| <li class="nav-item px-2"> | ||
| <NavLink class="nav-link" href="monitoring/jwttoken"> | ||
| <span class="@Icons.Credential"></span> JWT Token | ||
| </NavLink> | ||
| </li> | ||
| } |
| // There was an error trying to authenticate the user. Probably invalid credentials | ||
| errorMessage = ( authResponse.Data != null ? userConfig.GetApiText(authResponse.Data) : "Middleware Api Error: " + authResponse.Content ); | ||
| errorMessage = (authResponse.Content != null ? userConfig.GetApiText(authResponse.Content) : "Middleware Api Error: " + authResponse.Content); | ||
| } |
| { | ||
| "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", | ||
| "refreshToken": "a7f3c8b9e2d1...", | ||
| "accessTokenExpires": "2025-12-11T14:30:00Z", | ||
| "refreshTokenExpires": "2026-01-10T12:00:00Z" | ||
| } |
| { | ||
| "accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", | ||
| "refreshToken": "a7f3c8b9e2d1...", | ||
| "accessTokenExpires": "2025-12-11T14:30:00Z", | ||
| "refreshTokenExpires": "2026-01-10T12:00:00Z" | ||
| } |
|
tpurschke
left a comment
There was a problem hiding this comment.
last medium finding will be moved to a separate issue
| dotnet build | ||
| dotnet test --filter "Name=HtmlToPdfTest" | ||
|
|
||
| - name: Run JWT refresh integration tests |
There was a problem hiding this comment.
i guess we need to have a small meeting regarding this point today 12:30 pm?



Running tests with runsettings
The repository contains two
.runsettingsfiles for localdotnet testruns:FWO.default.runsettingskeepsFWO_RUN_INTEGRATION_TESTS=false. Use this for the normal unit test run so tests that require a local FWO environment stay skipped.FWO.local.runsettingsis intended for local integration scenarios. It setsFWO_RUN_INTEGRATION_TESTS=trueand readsFWO_TEST_USERNAMEplusFWO_TEST_PASSWORDfrom your local file. CopyFWO.local.runsettings.exampletoFWO.local.runsettingsand replace the placeholders with credentials that are valid in your local test environment.Typical commands:
This keeps the default test run safe for everyone while still making it easy to opt into the JWT integration tests locally when a prepared environment is available.
❕ Visual Studio: Select that file in Visual Studio via Test -> Configure Run Settings -> Select Solution Wide runsettings File. With FWO.local.runsettings active, the JWT integration tests are enabled in Test Explorer.
❕ VS Code: tasks(test_default, test_jwt_integration_local) were also added for running default tests and local JWT integration tests via "dotnet test --settings ..." or use Ctrl+Shift+P => Tasks: Run Task => then select "test_default" or "test_jwt_integration_local"
Task: test_jwt_integration_local (FWO.local.runsettings)
Task: test_default (FWO.default.runsettings)
❕ Please review: ReportJob and DailyCheckJob are affected due to internal jwt lifetime changes.
Under monitoring there will be a new page to view/refresh/revoke the current jwt tokens of the admin user. The page is only added in debug build because of security risks and only for users with admin role.
Closing #1689
Closing #4352