0

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.

Screenshot of decoded token: enter image description here

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);
      });
  }
dur
  • 15,689
  • 25
  • 79
  • 125
Nico
  • 1,727
  • 1
  • 24
  • 42
  • The `aud` being `00000003-0000-0000-c000-00000000000` usually indicates you're trying to use a Microsoft Graph scope. Can you show us your frontend configuration and which scopes you're requesting? If you're using MSAL.js (or the Angular/React variant), you probably have a `protectedResourceMap` somewhere containing the scopes you're requesting. – g00glen00b Aug 24 '23 at 06:18
  • 1
    @Nico check this if helpful https://stackoverflow.com/questions/76009655/using-an-azure-ad-tenant-id-and-a-valid-token-issued-for-a-app-registration/76013867#76013867 – Rukmini Aug 24 '23 at 06:21
  • 2
    @Nico The link provided by Rukmini contains the answer. You're using the `User.Read` scope while you should generate a scope for your app registration (see linked answer) and then use that one. These scopes usually follow the format `api:///`. Once you've done that, try to generate a new token and the `aud` claim should now be `api://`. For now I'm voting to close this question as a duplicate. – g00glen00b Aug 24 '23 at 06:32
  • Thanks a lot for the help and pointers! I will get to the azure admin and request for the necessary changes to be made. – Nico Aug 24 '23 at 06:34

0 Answers0