tests: Allow e2e to use injected token (cli) for UI access#1010
Conversation
Reviewer's GuideAdds a new Playwright UI authentication mode that injects an OAuth2 client-credentials access token into sessionStorage for OIDC-based SSO environments, shares auth configuration between API and UI tests (including scope), and documents the new environment variables and behaviour in the e2e README. Sequence diagram for Playwright UI token_injection auth flowsequenceDiagram
actor Tester
participant PlaywrightRunner as Playwright_Runner
participant Frontend as Frontend_UI
participant EntraID as Entra_ID
participant SessionStorage as SessionStorage
participant ReactApp as React_App
participant ReactOidcContext as React_OIDC_Context
Tester->>PlaywrightRunner: Run_e2e_tests_with_token_injection
PlaywrightRunner->>Frontend: HTTP_GET_TRUSTIFY_UI_URL_index_html
Frontend-->>PlaywrightRunner: index_html_with_window_env
PlaywrightRunner->>PlaywrightRunner: Parse_OIDC_SERVER_URL_and_OIDC_CLIENT_ID
PlaywrightRunner->>EntraID: POST_token_endpoint(client_id,client_secret,scope,grant_type_client_credentials)
EntraID-->>PlaywrightRunner: access_token
PlaywrightRunner->>PlaywrightRunner: Register_addInitScript_to_inject_token
PlaywrightRunner->>ReactApp: Launch_browser_page_to_TRUSTIFY_UI_URL
ReactApp->>SessionStorage: Read_oidc_user_entry
Note over PlaywrightRunner,SessionStorage: addInitScript_runs_before_app_load
PlaywrightRunner->>SessionStorage: Write_oidc_user_authority_clientId_with_access_token
ReactApp->>ReactOidcContext: Initialize_with_existing_session
ReactOidcContext->>SessionStorage: Load_oidc_user_state
ReactOidcContext-->>ReactApp: User_authenticated_from_injected_token
ReactApp-->>Tester: UI_loaded_without_login_redirect
Updated class diagram for UI auth helpers and shared constantsclassDiagram
class CommonConstants {
+string UI_AUTH_MODE
+string AUTH_SCOPE
+string TRUSTIFY_UI_URL
}
class EnvVariables {
+string PLAYWRIGHT_UI_AUTH_MODE
+string PLAYWRIGHT_AUTH_SCOPE
+string PLAYWRIGHT_AUTH_CLIENT_ID
+string PLAYWRIGHT_AUTH_CLIENT_SECRET
+string PLAYWRIGHT_AUTH_URL
+string TRUSTIFY_UI_URL
}
class UIAuthHelper {
+login(page)
+loginWithForm(page, username, password)
+loginWithTokenInjection(page, clientId, clientSecret, authUrl, scope, uiUrl)
}
class ApiAuthHelper {
+getClientCredentialsToken(authUrl, clientId, clientSecret, scope)
}
class TokenInjectionConfig {
+string authority
+string clientId
+string accessToken
+number expiresAt
+string oidcUserStorageKey
}
CommonConstants <.. EnvVariables : populated_from
UIAuthHelper --> CommonConstants : reads
UIAuthHelper --> EnvVariables : reads
UIAuthHelper --> ApiAuthHelper : reuses_client_credentials_flow
UIAuthHelper --> TokenInjectionConfig : builds_and_injects
TokenInjectionConfig ..> SessionStorageAdapter : stored_in
class SessionStorageAdapter {
+setItem(key, value)
+getItem(key)
}
Flow diagram for selecting UI auth mode and performing loginflowchart TD
Start([Start_UI_login])
ReadMode[Read UI_AUTH_MODE or PLAYWRIGHT_UI_AUTH_MODE]
CheckAuthRequired{AUTH_REQUIRED?}
ModeDecision{UI auth mode}
FormLogin[loginWithForm]
TokenLogin[loginWithTokenInjection]
End([Tests_continue_in_authenticated_state])
Start --> CheckAuthRequired
CheckAuthRequired -- false --> End
CheckAuthRequired -- true --> ReadMode
ReadMode --> ModeDecision
ModeDecision -- form_or_empty --> FormLogin
ModeDecision -- token_injection --> TokenLogin
FormLogin --> End
TokenLogin --> DiscoverOIDC[Fetch index_html_and_discover_OIDC_SERVER_URL_and_OIDC_CLIENT_ID]
DiscoverOIDC --> GetToken[Call_API_auth_helper_getClientCredentialsToken]
GetToken --> BuildPayload[Build_oidc_user_payload_and_storage_key]
BuildPayload --> InjectToken[addInitScript_to_write_payload_to_sessionStorage]
InjectToken --> End
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
discoverFrontendOidcConfighelper relies onatob, which is not guaranteed in all Node runtimes; consider usingBuffer.from(serverConfig, 'base64').toString('utf-8')to avoid environment-specific breakage. - Fetching the frontend HTML with
maxRedirects: 0and assuming the OIDC config is present on that first response may fail for deployments that redirect/to another path; allowing redirects or making the index path configurable would make token discovery more robust.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `discoverFrontendOidcConfig` helper relies on `atob`, which is not guaranteed in all Node runtimes; consider using `Buffer.from(serverConfig, 'base64').toString('utf-8')` to avoid environment-specific breakage.
- Fetching the frontend HTML with `maxRedirects: 0` and assuming the OIDC config is present on that first response may fail for deployments that redirect `/` to another path; allowing redirects or making the index path configurable would make token discovery more robust.
## Individual Comments
### Comment 1
<location path="e2e/tests/ui/helpers/Auth.ts" line_range="75-41" />
<code_context>
+const loginWithTokenInjection = async (page: Page) => {
</code_context>
<issue_to_address>
**issue (testing):** Add an e2e scenario that explicitly exercises `token_injection` auth mode
Currently `loginWithTokenInjection` is only reachable via `UI_AUTH_MODE`, but there’s no e2e coverage to verify it actually works or fixes the SSO/MFA scenario described in the PR. Please add a smoke test (or a small Playwright project) that runs with `PLAYWRIGHT_UI_AUTH_MODE=token_injection` and `AUTH_REQUIRED=true`, uses a known-good Entra ID client, and asserts the UI is authenticated without using the login form. This will both prevent regressions and prove the injected token is sufficient for UI access.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return { | ||
| oidcServerUrl: envInfo.OIDC_SERVER_URL, | ||
| oidcClientId: envInfo.OIDC_CLIENT_ID || "frontend", | ||
| }; |
There was a problem hiding this comment.
issue (testing): Add an e2e scenario that explicitly exercises token_injection auth mode
Currently loginWithTokenInjection is only reachable via UI_AUTH_MODE, but there’s no e2e coverage to verify it actually works or fixes the SSO/MFA scenario described in the PR. Please add a smoke test (or a small Playwright project) that runs with PLAYWRIGHT_UI_AUTH_MODE=token_injection and AUTH_REQUIRED=true, uses a known-good Entra ID client, and asserts the UI is authenticated without using the login form. This will both prevent regressions and prove the injected token is sufficient for UI access.
|
Notes:
|
Entra ID E2E Test Authentication via Token Injection
Problem
Running e2e tests against a TPA instance configured with Microsoft Entra ID as
OIDC provider is not possible when the only available user accounts are
corporate SSO-federated identities requiring 2FA. The existing e2e auth flow
fills a browser login form with
PLAYWRIGHT_AUTH_USER/PLAYWRIGHT_AUTH_PASSWORD,which cannot work with SSO/MFA-protected accounts. Creating local test users in
Entra ID is also not an option when user management permissions are restricted.
Solution
Added a
token_injectionauth mode for UI tests. Instead of filling a loginform, the test runner:
index.htmlto discoverOIDC_SERVER_URLand
OIDC_CLIENT_IDfrom thewindow._envconfiguration.client_credentialsgrant with an App Registration's
client_id+client_secret.sessionStorage(via Playwright'saddInitScript)using the key format
oidc.user:{authority}:{clientId}before the React apploads.
react-oidc-contextfinds the token insessionStorage, considers the user authenticated, and skips the OIDC login
redirect.
This requires an Azure App Registration with a client secret and appropriate
API permissions for the TPA backend, but does not require creating any user
accounts.
Files Changed
e2e/tests/common/constants.tsAdded three new exports:
UI_AUTH_MODE(PLAYWRIGHT_UI_AUTH_MODE) - Selects auth mode:form(default, existing behavior) or
token_injection.AUTH_SCOPE(PLAYWRIGHT_AUTH_SCOPE) - OAuth2 scope for theclient_credentialstoken request (e.g.,api://<app-id>/.default).TRUSTIFY_UI_URL(TRUSTIFY_UI_URL) - Frontend URL used to auto-discoverOIDC settings from the deployment.
e2e/tests/ui/helpers/Auth.tsRestructured into two auth paths dispatched by
UI_AUTH_MODE:loginWithForm()- Original browser form login, unchanged.loginWithTokenInjection()- New path using client credentials andsessionStorage injection.
login()- Public entry point that selects the mode.The token injection path reuses the existing
PLAYWRIGHT_AUTH_CLIENT_ID,PLAYWRIGHT_AUTH_CLIENT_SECRET, andPLAYWRIGHT_AUTH_URLenv vars (sharedwith API tests).
e2e/README.mdDocumented the new environment variables in the "For UI tests" table.
Usage
Permissions / Authorization
The
client_credentialstoken represents an application identity, not a user.Authorization depends on how the TPA backend is configured:
auth.yamlontrustd: If the backend'sauth.yamldirectly mapsthe API/CLI client ID to internal permissions (e.g.,
read.sbom,create.advisory), the token works without anyrolesclaim in the JWT.This is useful when you lack Entra ID admin permissions to grant app role
consent.
docs, the API App Registration needs app roles (
API App / create:document, etc.)assigned and admin-consented in API Permissions
so they appear in the token's
rolesclaim.The backend's
scopeMappingsthen maps these to internal permissions.Other Limitations
The UI will display the API client ID (e.g.,
660ae41f-...) as the usernamein the top-right corner, since the token has no user profile information. This
is cosmetic and does not affect test execution.
Summary by Sourcery
Add a new token injection authentication mode for Playwright UI tests that uses client credentials to obtain an access token and injects it into the browser session, while keeping the existing form-based login as the default behavior.
New Features:
Enhancements:
Documentation: