Enterprise Java

Spring Dynamic Authorization Scopes Example

Modern enterprise applications often require fine-grained access control where different users, clients, and business scenarios need different levels of permissions. OAuth 2.0 scopes provide a standard way to define what resources an application can access on behalf of a user. In traditional OAuth implementations, scopes are usually predefined and remain static. For example, an application may define scopes such as read, write, and admin. However, enterprise applications often require more flexible authorization models where scopes need to be generated, modified, or validated dynamically based on user roles, tenant information, subscription plans, or runtime business rules. Spring Authorization Server provides the foundation for building OAuth 2.0 and OpenID Connect providers with Spring Boot. By extending its authorization components, we can implement dynamic authorization scopes that are evaluated at runtime instead of relying only on static configuration.

1. Understanding Dynamic Scopes in Spring Authorization Server

An OAuth 2.0 scope represents a specific permission that a client application requests to access protected resources on behalf of a user. During the authorization flow, the client sends the required scopes along with the authorization request, and the authorization server validates these scopes based on the registered client configuration and user permissions before issuing an access token. In the traditional approach, scopes are predefined and remain static. The authorization server only allows scopes that are configured in advance for the client application.

Client Request

GET /oauth2/authorize?response_type=code&client_id=flight-app&scope=flight.read%20flight.write

Authorization Server

Allowed Scopes:
- flight.read
- flight.write

In this approach, the same set of scopes is applied for every user of the client. Dynamic authorization scopes extend this model by allowing the authorization server to evaluate user context, roles, and business rules at runtime before granting permissions. In the traditional OAuth 2.0 authorization model, scopes are fixed and configured during application startup. Dynamic authorization scopes introduce runtime decision-making, where scopes can be generated based on user roles, depend on tenant or organization context, change according to business rules, and be filtered or adjusted before issuing an access token.

1.1 Example Use Case

Consider an airline maintenance application where different users require different levels of access based on their responsibilities. An aircraft engineer may need permissions to view and update aircraft information, while a manager may require additional approval privileges. Similarly, an auditor may only need read-only access to aircraft and audit records. Dynamic authorization scopes allow the system to assign these permissions at runtime based on the user’s role.

UserRoleDynamic Scopes
Aircraft EngineerENGINEERaircraft.read, aircraft.update
ManagerMANAGERaircraft.read, aircraft.update, aircraft.approve
AuditorAUDITORaircraft.read, audit.read

1.2 Benefits of Dynamic Authorization Scopes

  • Fine-grained authorization control.
  • Supports multi-tenant applications.
  • Reduces static security configuration.
  • Enables role-based and attribute-based authorization.
  • Supports changing business rules without redeploying applications.

2. Implementing Dynamic Authorization Scopes with Spring Boot

We will create a Spring Authorization Server application that dynamically generates scopes based on the authenticated user’s role.

2.1 Maven Dependency

The example uses Spring Boot 3.x with Spring Authorization Server and Spring Security 6.x. Spring Authorization Server provides OAuth 2.0 and OpenID Connect protocol implementation while integrating seamlessly with Spring Security.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-authorization-server</artifactId>
    </dependency>
</dependencies>

2.2 Spring Boot Application

package com.example.authorizationserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AuthorizationServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(
            AuthorizationServerApplication.class,
            args
        );
    }
}

The @SpringBootApplication annotation enables Spring Boot auto-configuration, component scanning, and configuration support for the application. The main() method acts as the entry point of the Spring Authorization Server application and uses SpringApplication.run() to initialize the Spring application context, load all required beans, and start the embedded web server. Once started, the application is ready to handle OAuth 2.0 authorization requests, authenticate users, and issue access tokens based on the configured authorization flow.

2.2.1 Application Configuration

server.port=8080

spring.application.name=dynamic-scope-authorization-server

logging.level.org.springframework.security=DEBUG

The application configuration defines the server port and application name. Enabling Spring Security debug logging helps developers understand authentication flow, authorization decisions, and scope validation during development.

2.3 Authorization Server Configuration

The following configuration creates an OAuth2 authorization server with a registered client. It enables the default Spring Authorization Server security configuration and registers an OAuth2 client with initially allowed scopes. These client scopes act as the maximum permissions that can be requested. The final user permissions are calculated dynamically during token generation based on roles and business rules.

package com.example.authorizationserver.config;

import java.util.UUID;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;

@Configuration
public class AuthorizationServerConfig {

    @Bean
    SecurityFilterChain authorizationServerSecurityFilterChain(
            HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration
                .applyDefaultSecurity(http);
        return http.build();
    }

    @Bean
    RegisteredClientRepository registeredClientRepository() {
        RegisteredClient client =
                RegisteredClient.withId(
                        UUID.randomUUID().toString()
                )
                .clientId("aircraft-app")
                .clientSecret("{noop}secret")
                .authorizationGrantType(
                        AuthorizationGrantType.AUTHORIZATION_CODE
                )
                .redirectUri(
                        "http://localhost:8080/callback"
                )
                .scope("aircraft.read")
                .scope("aircraft.update")
                .scope("aircraft.approve")
                .build();

        return new InMemoryRegisteredClientRepository(client);
    }
}

The @Configuration annotation marks this class as a Spring configuration component that defines application beans. The authorizationServerSecurityFilterChain() method applies Spring Authorization Server’s default security settings, which configure OAuth 2.0 endpoints such as authorization, token, and consent endpoints. The registeredClientRepository() method creates an OAuth2 client named aircraft-app and stores its details in an in-memory repository. The registered client defines authentication details, supported authorization grant type, redirect URI, and allowed scopes such as aircraft.read and aircraft.update. During the authorization flow, Spring Authorization Server validates the client details and ensures that only configured scopes can be requested before issuing an authorization code and access token.

2.4 Creating Dynamic Scope Service

The dynamic scope service determines the allowed scopes at runtime based on the authenticated user’s roles and permissions. Instead of assigning the same permissions to every user, this approach evaluates user information and generates only the scopes required for that specific user.

package com.example.authorizationserver.service;

import java.util.HashSet;
import java.util.Set;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

@Service
public class DynamicScopeService {

    public Set<String> getScopes(UserDetails user) {
        Set<String> scopes = new HashSet<>();

        boolean isEngineer =
                user.getAuthorities()
                    .stream()
                    .anyMatch(authority ->
                        authority.getAuthority()
                        .equals("ROLE_ENGINEER")
                    );

        if (isEngineer) {
            scopes.add("aircraft.read");
            scopes.add("aircraft.update");
        }

        boolean isManager =
                user.getAuthorities()
                    .stream()
                    .anyMatch(authority ->
                        authority.getAuthority()
                        .equals("ROLE_MANAGER")
                    );

        if (isManager) {
            scopes.add("aircraft.approve");
        }

        return scopes;
    }
}

The DynamicScopeService is a Spring service component responsible for calculating user-specific OAuth2 scopes during the authorization process. The getScopes() method accepts the authenticated user’s UserDetails object and evaluates the assigned roles using Spring Security authorities. If the user has the ROLE_ENGINEER role, the service grants aircraft.read and aircraft.update permissions. Similarly, users with the ROLE_MANAGER role receive an additional aircraft.approve permission. This runtime-based scope generation enables fine-grained authorization where access tokens contain only the permissions applicable to the current user.

2.5 Security Configuration with Dynamic Scope Support

The following security configuration creates sample users with different roles. These roles are later used by the dynamic scope service to determine which permissions should be granted during the OAuth2 authorization flow.

package com.example.authorizationserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.core.userdetails.UserDetailsService;

@Configuration
public class SecurityConfig {

  @Bean
  UserDetailsService users() {
    UserDetails engineer = User.withUsername("john").password("{noop}password").roles("ENGINEER").build();
    UserDetails manager = User.withUsername("mary").password("{noop}password").roles("MANAGER").build();

    return new InMemoryUserDetailsManager(engineer, manager);
  }
}

The SecurityConfig class configures user authentication for the authorization server. The UserDetailsService bean creates two in-memory users, where john is assigned the ENGINEER role and mary is assigned the MANAGER role. These roles are stored as Spring Security authorities and are later evaluated by the dynamic scope service to generate user-specific OAuth2 scopes. In a real enterprise application, this user information would typically come from an identity provider, database, or directory service instead of an in-memory configuration.

2.6 Token Customization with Dynamic Scopes

Spring Authorization Server allows customization of JWT access tokens using an OAuth2 token customizer. The generated dynamic scopes are added as claims inside the access token during token creation so that downstream resource servers can validate user permissions.

package com.example.authorizationserver.config;

import java.util.Set;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;

import com.example.authorizationserver.service.DynamicScopeService;


@Configuration
public class TokenCustomizationConfig {

    private final DynamicScopeService scopeService;

    public TokenCustomizationConfig(DynamicScopeService scopeService) {
        this.scopeService = scopeService;
    }

    @Bean
    OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
        return context -> {
            if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
                Authentication authentication = context.getPrincipal();

                UserDetails user = (UserDetails) authentication.getPrincipal();

                Set<String> scopes = scopeService.getScopes(user);
                context.getClaims().claim("scope", scopes);
            }
        };
    }
}

The TokenCustomizationConfig class customizes the JWT access token generation process in Spring Authorization Server. The OAuth2TokenCustomizer<JwtEncodingContext> bean is invoked automatically by Spring during token creation. The implementation first checks whether the current token being generated is an access token using OAuth2TokenType.ACCESS_TOKEN. It then retrieves the authenticated user’s details from the Authentication object and passes the user information to the DynamicScopeService to calculate the allowed scopes dynamically. Finally, the generated scopes are added as a scope claim inside the JWT using context.getClaims().claim(). As a result, the issued access token contains only the permissions applicable to the current user, enabling resource servers to perform fine-grained authorization based on dynamically generated scopes.

2.7 Resource Server Security Configuration

package com.example.authorizationserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class ResourceServerConfig {

    @Bean
    SecurityFilterChain resourceServerSecurity(
            HttpSecurity http) throws Exception {

        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/aircraft/**")
                .authenticated()
                .anyRequest()
                .permitAll()
            )
            .oauth2ResourceServer(
                oauth2 -> oauth2.jwt()
            );

        return http.build();
    }
}

The resource server configuration enables JWT-based authentication for protected APIs. The oauth2ResourceServer() configuration instructs Spring Security to validate incoming Bearer tokens using JWT validation. When a request contains an access token, Spring Security extracts the scope claim and converts scopes into authorities with the SCOPE_ prefix, which can then be used with annotations such as @PreAuthorize.

2.8 API Authorization Example

The resource API uses Spring Security scope-based authorization to protect aircraft information. Only users whose access token contains the required aircraft.read scope are allowed to access this endpoint.

package com.example.authorizationserver.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.access.prepost.PreAuthorize;

@RestController@RequestMapping("/api/aircraft")
public class AircraftController {

  @GetMapping("/{id}")@PreAuthorize("hasAuthority('SCOPE_aircraft.read')")
  public String getAircraft(@PathVariable String id) {
    return "Aircraft Details : " + id;

  }
}

The AircraftController exposes a REST endpoint to retrieve aircraft details. The @PreAuthorize annotation enables method-level security and checks whether the authenticated user’s access token contains the aircraft.read scope. Spring Security converts OAuth2 scopes from the JWT access token into authorities with the SCOPE_ prefix, allowing the application to perform fine-grained authorization checks. If the user has the required scope, the API request is processed successfully; otherwise, access is denied with an authorization error response.

2.9 Running the Application

Before running the application, ensure that Java 17 or above and Maven are installed on the system. The application can be started using the Spring Boot Maven plugin. Execute the following command from the project root directory:

mvn spring-boot:run

Once the application starts successfully, Spring Boot initializes the authorization server, registers the OAuth2 client, loads the configured users, and exposes the OAuth2 authorization endpoints. By default, the authorization server runs on port 8080.

Started AuthorizationServerApplication in 4.5 seconds
Tomcat started on port 8080
OAuth2 Authorization Server started successfully

2.10 Requesting Authorization Code

The client application initiates the OAuth2 authorization flow by redirecting the user to the authorization endpoint. The authorization request contains the client identifier, requested response type, redirect URI, and required scopes.

GET http://localhost:8080/oauth2/authorize?response_type=code&client_id=aircraft-app&scope=aircraft.read%20aircraft.update&redirect_uri=http://localhost:8080/callback

The user is redirected to the Spring Authorization Server login page. After successful authentication, the authorization server validates the client details, authenticates the user, and evaluates the user’s roles using the dynamic scope generation logic. The effective permissions are calculated by the DynamicScopeService and are later added to the access token during token generation.

2.11 Authorization Code Exchange

After successful authentication and user consent, the authorization server returns an authorization code to the configured redirect URI. The client application exchanges this authorization code for an access token by calling the token endpoint.

POST http://localhost:8080/oauth2/token

Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTHORIZATION_CODE&
client_id=aircraft-app&
client_secret=secret&
redirect_uri=http://localhost:8080/callback

The authorization server validates the authorization code, client credentials, and redirect URI. After successful validation, it generates a JWT access token containing the dynamically calculated scopes based on the authenticated user’s role.

2.12 Dynamic Scope Generation Output

When an engineer user logs in, the authorization server identifies the ROLE_ENGINEER authority and generates only the scopes assigned to that role.

User:

Username: john
Role: ENGINEER

Generated Dynamic Scopes:

[
  "aircraft.read",
  "aircraft.update"
]

When a manager user authenticates, the authorization server grants additional approval permissions because the user has the ROLE_MANAGER authority.

User:

Username: mary
Role: MANAGER

Generated Dynamic Scopes:

[
  "aircraft.read",
  "aircraft.update",
  "aircraft.approve"
]

2.13 Access Token Response

The token endpoint returns an access token containing the dynamically generated permissions for the authenticated user. For example, when the engineer user john authenticates, the generated JWT contains only the scopes assigned to the ENGINEER role.

{
  "access_token": "eyJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": [
    "aircraft.read",
    "aircraft.update"
  ]
}

The access token contains the dynamically generated scope claim. Resource servers use this claim to validate whether the authenticated user has sufficient permissions to access protected APIs.

2.14 Calling Protected API

The client application uses the generated access token to call the protected aircraft API. The token is sent in the HTTP Authorization header using the Bearer token format.

GET /api/aircraft/1001

Host: localhost:8080

Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

Since the access token contains the aircraft.read scope, Spring Security successfully authorizes the request.

HTTP/1.1 200 OK

Aircraft Details : 1001

2.15 Unauthorized API Access

If a user does not have the required scope, Spring Security blocks the request before executing the controller method.

GET /api/aircraft/1001

Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...

HTTP/1.1 403 Forbidden

{
  "error": "insufficient_scope",
  "message": "Access denied"
}

The request is rejected because the JWT token does not contain the required aircraft.read scope. A custom AccessDeniedHandler can be configured if a structured JSON error response is required.

3. Conclusion

Dynamic authorization scopes enable flexible and secure access control for enterprise applications by allowing permissions to be calculated at runtime based on user roles, attributes, and business rules instead of relying on a predefined permission model. Spring Authorization Server provides extension points such as custom authorization providers, token customization, and Spring Security integration to implement dynamic scope-based authorization. When combined with JWT tokens and resource server security, dynamic scopes provide a scalable and maintainable security approach for modern cloud-native applications, microservices, and enterprise platforms.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button