I have a Java 17 Spring Boot 3 backend which receives an Azure AD OAuth2 token ( access token received from Angular MSALJ 2.0 library @azure/msal-browser + @azure/msal-react ).
In Azure AD I have an registered App which is set up as SPA
( Single Page Application ) and has no credentials defined. The application itself is configured to give out ID Tokens used for implicit and hybrid flows.
The Redirect URIs are configured to the home path of the Frontend application ( 2 versions, 1 for localhost and 1 for cloud deployed version )
The Token permission is set to User.Read
in order to retrieve the profile details.
There is no exposed API configured.
The Frontend part works successfully.
Upon Login I'm redirected to the Azure Login and I can see I received a bunch of tokens ( access_token
, refresh_token
, id_token
etc. ) being returned by the /token
call.
Now I'm grabbing the access_token and send it to my BE for an API call.
However on the BE I have the problem that the token cannot be validated with the following log message of org.springframework.security
on DEBUG
:
AuthenticationWebFilter : Authentication failed: Failed to validate the token
I facilitated a multiple microsoft docs and also Baeldung and even Chat-GPT but all the variations don't seem to work for me, or rather it seems I'm missing something.
Here is my gradle dependency:
// Spring
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-security"
.....
implementation "org.springframework.security:spring-security-oauth2-client"
implementation "org.springframework.security:spring-security-oauth2-jose"
implementation "org.springframework.security:spring-security-oauth2-resource-server"
implementation "org.springframework.session:spring-session-core"
.....
// Azure AD
implementation 'com.azure.spring:spring-cloud-azure-starter-active-directory:5.4.0'
implementation 'com.azure.spring:spring-cloud-azure-starter:5.4.0'
The two azure dependencies have been attempted standalone or in combination, however there seems to be no difference.
Following now is my application.yml config:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://sts.windows.net/<TENANT-ID>/
jwk-set-uri: https://login.microsoftonline.com/<TENANT-ID>/discovery/v2.0/keys
cloud:
azure:
profile:
tenant-id: <TENANT-ID>
cloud-type: Azure
credential:
managed-identity-enabled: true
client-id: <APPLICATION-ID>
I have also previously used https://login.microsoftonline.com/<TENANT-ID>/v2.0
for the issuer-uri
but after decoding the access token I found out that the iss was using sts.windows.net
as host
Lastly my security configuration in Spring:
@Bean
@Order(2)
public SecurityWebFilterChain apiSecurity(
ServerHttpSecurity http,
OAuth2ResourceServerConfigurer oAuth2ResourceServerConfigurer) {
http.securityMatcher(API_MATCHER)
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated();
oAuth2ResourceServerConfigurer.configure(http);
return http.build();
}
Snippet of the oAuth2ResourceServerConfigurer:
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwksUrl;
public void configure(ServerHttpSecurity http) {
http.oauth2ResourceServer(
oauth2ResourceServer -> oauth2ResourceServer.jwt(
jwt -> jwt.jwkSetUri(jwksUrl)));
}
The security configuration seems to be somewhat correct as my test version of the configurer behaves correctly during integration tests.
If I make a call to an endpoint without any mock JWT it returns HTTP 401 Unauthorized
and when I provide a mock JWT the call returns HTTP 200 OK
as expected.
I sadly cannot look at Azure AD Logs and all I can control is the FE and BE.
TL;DR:
FE takes care of Azure AD Authentication and login, then sends the access token to the BE.
BE tries to validate token with oAuth2ResourceServer pointing to Azure AD tenant and application.
Token validation fails. All requests are rejected with HTTP 401 Unauthorized
.
Frontend configuration:
AuthConfig component:
export const msalConfig: Configuration = {
auth: {
clientId: authCredentials.clientId,
authority:
'https://login.microsoftonline.com/<TENANT-ID>/',
redirectUri: window.location.origin + process.env.PUBLIC_URL,
postLogoutRedirectUri: window.location.origin + process.env.PUBLIC_URL,
},
system: {
allowNativeBroker: false, // Disables WAM Broker
},
};
// Add here scopes for id token to be used at MS Identity Platform endpoints.
export const loginRequest: PopupRequest = {
scopes: ['User.Read'],
};
// Add here the endpoints for MS Graph API services you would like to use.
export const graphConfig = {
graphMeEndpoint: 'https://graph.microsoft.com/v1.0/me',
};
Index.tsx
export const msalInstance = new PublicClientApplication(msalConfig);
While graphConfig is being defined here it is never being used/referenced in the FE project. Additionally the FE wasn't written by me and I'm continuing work on it.
Debug button being used for testing and verification:
async function handleSubmit(event: any) {
const account = msalInstance.getActiveAccount();
if (!account) {
throw Error(
'No active account! Verify a user has been signed in and setActiveAccount has been called.'
);
}
const response = await msalInstance.acquireTokenSilent({
...loginRequest,
account: account,
});
const headers = new Headers();
const bearer = `Bearer ${response.accessToken}`;
headers.append('Authorization', bearer);
const options = {
method: 'GET',
headers: headers,
};
return fetch(`${backendBaseUrl}/v2/debug/user`, options)
.then(response => response)
.catch(error => {
console.log(error);
});
}