Keycloak resource servers don’t just protect your APIs; they define what parts of your API can be accessed by whom, using the concept of scopes.
Let’s see this in action. Imagine a simple API endpoint that retrieves user profile information. We’ll use Keycloak to protect it, requiring a specific scope to grant access.
Here’s a sample Java Spring Boot application snippet demonstrating how to secure an endpoint:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping("/{userId}")
@PreAuthorize("hasScope('view-profile')") // This is the key!
public ResponseEntity<String> getUserProfile(@PathVariable String userId) {
// In a real app, you'd fetch user data from a database
return ResponseEntity.ok("Profile for user: " + userId);
}
}
In this code, @PreAuthorize("hasScope('view-profile')") is the magic. When a request comes in, Keycloak (acting as the authorization server) will issue an access token. This token will contain claims, including the scopes the authenticated user is authorized to use. The Spring Security filter chain, integrated with Keycloak, checks if the view-profile scope is present in the token’s claims. If it is, the request proceeds; otherwise, it’s rejected with a 403 Forbidden.
Now, let’s look at how this is configured within Keycloak itself.
First, you need a Client in Keycloak representing your API. This is your "resource server" from Keycloak’s perspective.
-
Create a Client: Navigate to your Realm, then "Clients," and click "Create."
- Client ID:
my-api-client(This is what your application will use to identify itself). - Client Protocol:
openid-connect - Root URL:
http://localhost:8080(The base URL of your API). - Valid Redirect URIs:
*(For simplicity in this example; in production, be more restrictive). - Web Origins:
*(Again, for simplicity). - Access Type:
confidential(This is important for secure client authentication).
- Client ID:
-
Define Scopes: Within your
my-api-client, go to the "Scopes" tab.- Keycloak automatically creates a default scope, often named after the Client ID (
my-api-client). - Click "Add Scope" to create new scopes. Let’s add
view-profile.- Name:
view-profile - Description:
Allows viewing user profiles
- Name:
- You can add other scopes like
edit-profile,read-orders, etc., to represent different API functionalities.
- Keycloak automatically creates a default scope, often named after the Client ID (
-
Configure Roles (Optional but Recommended): While scopes are used for fine-grained authorization within an API, roles are often used to grant access to those scopes.
- Go to your Realm’s "Roles" section.
- Click "Add Role."
- Name:
api-user - Description:
Standard user for the API
- Name:
- Now, go back to your
my-api-client, select the "Scope" tab, and click "Add Role" under the "Roles" section. Selectapi-user. This means users assigned theapi-userrole can potentially be granted the scopes associated with this client.
-
Assign Roles to Users:
- Go to "Users," select a user, and navigate to the "Role Mappin g" tab.
- Under "Client Roles," select
my-api-clientand assign theapi-userrole.
-
Client Authentication: For your API client (
my-api-client) to authenticate with Keycloak and request tokens, it needs credentials.- Go to your
my-api-clientsettings. - Under the "Credentials" tab, you’ll find a "Secret." This secret should be securely stored in your API application’s configuration (e.g.,
application.propertiesor environment variables).
- Go to your
When a user logs in through Keycloak, they receive an access token. This token is a JWT (JSON Web Token). If the user has the api-user role, and that role is configured to grant the view-profile scope for my-api-client, the token will contain a scope claim like this:
{
// ... other claims
"scope": "openid profile view-profile", // Example scope claim
// ...
}
The Spring Security configuration in your application would look something like this (simplified):
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/api/v1/users/**").hasAuthority("SCOPE_view-profile") // Alternative to @PreAuthorize
.anyRequest().authenticated();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
Here, hasAuthority("SCOPE_view-profile") is another way to check for the presence of the view-profile scope. The SCOPE_ prefix is a convention often used by Spring Security when mapping scopes to authorities.
The true power here is that you can have multiple clients, each with its own set of scopes, and a single user can be granted different scopes across different clients based on their roles. This allows for a highly granular and flexible API security model, where you’re not just saying "this user can access the API," but rather "this user can access this specific part of this specific API."
What most people don’t realize is that the scope claim in a JWT is just a space-separated string of identifiers. There’s no inherent semantic meaning to view-profile or edit-profile within the token itself; their meaning is defined by the resource server (your API) and enforced by its security configuration. Keycloak’s role is to issue tokens that claim these scopes are granted, and your resource server’s role is to verify those claims and act accordingly.
The next step is often understanding how to revoke access to these scopes dynamically, which leads into concepts like refresh tokens and token revocation endpoints.