Wim Deblauwe
Wim Deblauwe

Reputation: 26858

How to secure Spring Boot REST API with Azure AD B2C?

I am using Spring Boot 2.2.0 with azure-active-directory-b2c-spring-boot-starter 2.2.0. I managed to secure a Thymeleaf web page with that (following their tutorial). Now, I want to have a REST API that is secured in the same way, as the actual application will be a mobile app that does REST calls to my Spring Boot backend.

I already figured out how to get a token with the password grant flow using:

POST https://<my-tenant-id>.b2clogin.com/<my-tenant-id.onmicrosoft.com/oauth2/v2.0/token?p=B2C_1_<my-custom-policy>

(with username and password as parameters)

So a mobile app could use that call. But how should I configure my Spring Boot app so that using Authorization: Bearer <access-token> on API calls works? What dependencies/starters do I need and how should I configure things?

UPDATE:

I tried adding:

<dependency>
  <groupId>org.springframework.security.oauth</groupId>
  <artifactId>spring-security-oauth2</artifactId>
  <version>2.3.7.RELEASE</version>
</dependency>

With:

@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class OAuth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("my-azure-b2c-test");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**")
            .authenticated();
    }
}

But when I do a request on my Spring Boot app, I get a 401 with "invalid_token" error.

Upvotes: 5

Views: 7507

Answers (1)

Wim Deblauwe
Wim Deblauwe

Reputation: 26858

Solution seems to be quite simple once you know it.

First, add the following dependencies:

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>

Next specify the spring.security.oauth2.resourceserver.jwt.jwk-set-uri property in your application.properties file.

To know this value, do a GET (using cURL or other tool) on https://<my-tenant-id>.b2clogin.com/<my-tenant-id>.onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_<my-custom-policy>

This will return a JSON body with jwks_uri value. Take that value and put it in your application.properties file.

Now create this Java class in the project:

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**")
            .authenticated()
            .and()
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    }
}

If you now have controller like this:

@RestController
public class ApiController {

    @GetMapping("/api/test")
    public String apiTest(@AuthenticationPrincipal Principal principal) {
        return "test " + principal;
    }
}

You will see that the principal is not-null if you do a GET on api/test with a correct Authorization header (and it is of type org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken)

The only thing that is unfortunate is that the Principal has no authorities, something I still need to figure out why.

Upvotes: 5

Related Questions