feat: MS Entra Bearer token authentication (#127)#158
feat: MS Entra Bearer token authentication (#127)#158oto-macenauer-absa merged 9 commits intomasterfrom
Conversation
Allow users holding a valid MS Entra (Azure AD) JWT to exchange it for a login-service access+refresh token pair via the existing /token/generate endpoint — alongside Basic Auth, LDAP, and Kerberos. Implementation details: - MsEntraConfig: PureConfig case class with tenant-id, client-id, audience, order and optional attributes map; implements ConfigValidatable - MsEntraTokenValidator: validates Entra JWTs via OIDC discovery + Nimbus JOSE; JWKS cached 1h via Guava LoadingCache; injectable JWKSource for tests - MsEntraBearerTokenFilter: OncePerRequestFilter; intercepts Authorization: Bearer headers; populates SecurityContext on success; returns 401 on failure - SecurityConfig: conditionally registers the filter before BasicAuthFilter when entra.order > 0 - AuthConfigProvider/ConfigProvider: getMsEntraConfig plumbing - Dependencies: add Guava CacheBuilder to apiDependencies - example.application.yaml: commented entra config block - Tests: MsEntraConfigTest, MsEntraTokenValidatorTest (real RSA key pair, no HTTP), MsEntraBearerTokenFilterTest (Mockito); all 153 tests pass
|
Report: Report: api - scala:2.12.17
|
- Replace single 'audience: String' with 'audiences: List[String]' in MsEntraConfig - Empty list means accept any audience from the tenant (only issuer + signature verified) - Non-empty list requires token aud to intersect with configured audiences - Audience check is done manually (post-processing) to support 'any of' semantics, since Nimbus DefaultJWTClaimsVerifier uses 'all of' semantics - Add run/fork and run/baseDirectory to build.sbt to fix sbt run with TomcatPlugin - Add run/javaOptions for macOS Keychain trust store (corporate proxy SSL)
- Add MsEntraGraphClient: calls Graph API with client credentials to look up onPremisesSamAccountName and onPremisesDomainName for the authenticated user, returning NETBIOS\samAccountName format - Add GraphUsernameResolver trait for testability - Add clientSecret and domains fields to MsEntraConfig (both optional; when absent, username resolution falls back to UPN from the token) - MsEntraTokenValidator: use graph resolver when clientSecret is configured; fall back to preferred_username/upn/sub when None - Add two tests: graph resolves username, graph returns None → UPN fallback - Update example.application.yaml with new config fields documentation - Add User.Read.All application permission to Login Service - DEV app Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Return lowercase samAccountName values from Graph-backed Entra resolution, keep domain mappings as the allow-list for recognized domains, and update logging/tests/docs to reflect the new behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Updated this PR to normalize Graph-resolved Entra usernames to lowercase What changed:
What this allows:
|
dk1844
left a comment
There was a problem hiding this comment.
LGTM so far
I would like to test it with our LS dev with our MS Entra AppId + Object ID
Add focused Graph client tests for username normalization and error branches, and make endpoint URLs injectable for realistic local HTTP coverage tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace real-looking sample identifiers in MsEntraGraphClientTest with made-up usernames and email UPNs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| tokenEndpointOverride.getOrElse(s"https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/token") | ||
|
|
||
| private val graphUsersBaseUrl = "https://graph.microsoft.com/v1.0/users" | ||
| private val graphUsersBaseUrl = graphUsersBaseUrlOverride.getOrElse("https://graph.microsoft.com/v1.0/users") |
There was a problem hiding this comment.
I strongly believe that these should not be in code. I know these are fallbacks but even so.
There was a problem hiding this comment.
Addressed in 3d1de53. All hardcoded Microsoft endpoint URLs have been moved into MsEntraConfig as two optional fields with public-cloud defaults:
loginBaseUrl(default:https://login.microsoftonline.com)graphBaseUrl(default:https://graph.microsoft.com)
All derived URLs (token endpoint, Graph users path, Graph scope, OIDC discovery URL, expected JWT issuer) are now computed from these config values. The test constructor overrides (tokenEndpointOverride/graphUsersBaseUrlOverride) have been removed — tests now pass custom base URLs via MsEntraConfig directly. The example.application.yaml documents both fields.
Hardcoded Microsoft endpoint URLs have been removed from code. Two new optional fields on MsEntraConfig with public-cloud defaults: loginBaseUrl (default: https://login.microsoftonline.com) graphBaseUrl (default: https://graph.microsoft.com) All derived URLs (token endpoint, Graph users path, Graph scope, OIDC discovery URL, expected JWT issuer) are now computed from these config values. Sovereign-cloud deployments (e.g. Azure Government) can override them without touching code. MsEntraGraphClient constructor overrides (tokenEndpointOverride, graphUsersBaseUrlOverride) are removed; tests now pass custom base URLs via MsEntraConfig directly. Addresses review comment on PR #158. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dk1844
left a comment
There was a problem hiding this comment.
LGTM (read the code). Looking forward to start experimenting with it.
Summary
Adds MS Entra (Azure AD) as a new login mechanism. Users holding a valid Entra Bearer JWT can now exchange it for a login-service access+refresh token pair alongside existing Basic Auth, LDAP and Kerberos methods.
Changes
MsEntraConfig— PureConfig case class (tenant-id,client-id,audience,order, optionalattributesmap); implementsConfigValidatableMsEntraTokenValidator— validates Entra JWTs via OIDC discovery + Nimbus JOSE; JWKS cached 1 h via GuavaLoadingCache; injectableJWKSourceoverride for tests (no HTTP calls)MsEntraBearerTokenFilter— SpringOncePerRequestFilter; interceptsAuthorization: Bearerheaders; populatesSecurityContexton success, returns HTTP 401 on failure; skips when context already populatedSecurityConfig— conditionally registers the filter beforeBasicAuthenticationFilterwhenentra.order > 0AuthConfigProvider/ConfigProvider—getMsEntraConfigplumbingDependencies— GuavaCacheBuilderadded toapiDependenciesexample.application.yaml— commentedentra:config block addedTests
Three new test files, all 153 tests pass:
MsEntraConfigTest— validation logicMsEntraTokenValidatorTest— real RSA key pair, injectedImmutableJWKSet, no HTTPMsEntraBearerTokenFilterTest— Mockito mock validator,MockHttpServletRequest/ResponseRelease notes:
Configuration
To enable, add to
application.yaml:Closes #127