Skip to main content

Add fine-grained authorization to a Spring Boot app

Build a Spring Boot REST controller for a blogging platform that registers users in Permit.io and allows only users with the Author role to create posts. This tutorial is for Java backend developers who want to enforce Permit.io policies from Spring Boot endpoints.

When you finish, your Spring Boot app has two endpoints:

EndpointWhat it does
POST /registerSyncs a user to Permit.io and assigns the user the Reader role in the default tenant
POST /postsCalls permit.check() and returns 403 unless the user in the request body has permission to create a Post

Prerequisites

1. Configure the policy in Permit

Create the blogging platform policy with the Permit CLI. If your environment already has a policy with a Post resource and an Author role that can create posts, skip to 2. Get your API key.

1

Install the Permit CLI

The Permit CLI creates policies and runs the PDP from your terminal. Install the CLI with npm:

npm install -g @permitio/cli

Run permit to confirm that the CLI is installed.

2

Sign in with the Permit CLI

Authenticate the CLI with your Permit.io account:

permit login

The command opens a browser window where you sign in. After you sign in, the CLI uses your default environment. To use a different environment, run permit env select and choose the environment.

3

Apply the blogging platform template

Permit CLI templates create a policy with predefined resources, roles, and rules. To see the available templates, run permit env template list. The template source files are in the Permit CLI repository.

Terminal output of the Permit CLI template list command showing the available policy templates

Apply the blogging-platform template to your environment:

permit env template apply --template blogging-platform

The CLI prints a success message when the template is applied.

4

Review the policy in the Policy Editor

In the Permit dashboard, select your project and open the Policy screen.

Permit Policy Editor showing the Post and Comment resources with permissions for the Admin, Reader, Author, and Premium Reader roles

The blogging-platform template creates:

Policy elementWhat the template defines
ResourcesPost (with a premium boolean attribute) and Comment, each with create, read, update, and delete actions
RolesAdmin (all actions), Author (create and read posts, read comments), Reader (create and read comments), and Premium Reader (read posts and comments)
RelationshipA Post is the parent of its Comment instances. An Author of a post instance becomes a Moderator of the comments on that post. This rule is relationship-based access control (ReBAC).
Resource setFree Post contains posts where premium is false. Readers can read free posts. This rule is attribute-based access control (ABAC).

This tutorial uses one rule from the policy: the Author role can create a Post, and the Reader role cannot. To change which role can perform an action, check or clear the box in the Policy Editor.

2. Get your API key

Your Spring Boot app and the PDP authenticate with Permit.io with your environment API key. Copy the API key of the environment where you applied the template. See Get your API key.

Keep the API key out of your code

Anyone with the environment API key can change that environment's policy through the Permit API. Load the key from an environment variable, and don't commit it.

3. Run the PDP

The PDP evaluates each permission check against your policy. Start a PDP container with the Permit CLI:

permit pdp run

The command starts the PDP in Docker and prints the container ID and name. The PDP listens on port 7766, so your app connects to it at http://localhost:7766.

Terminal output of permit pdp run showing the PDP container details

The Free Post resource set is an ABAC rule, and the Cloud PDP doesn't evaluate ABAC rules, so run the container PDP for this policy. To run the container with docker run instead, or to check that the PDP is healthy, see Run the PDP.

4. Build the Spring Boot app

1

Install the Java SDK

Add the Permit Java SDK dependency to your pom.xml file:

<dependency>
<groupId>io.permit</groupId>
<artifactId>permit-sdk-java</artifactId>
<version>2.2.5</version>
</dependency>

Run mvn clean install to download the dependency. The Java SDK is published to Maven Central as io.permit:permit-sdk-java. To use a later release, change the version value. For all SDK options, see Check permissions with the Java SDK.

2

Check the application class

Your main application class starts Spring Boot. A project generated with Spring Initializr already has a class like this one:

package com.example.permitdemo;

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

@SpringBootApplication
public class PermitdemoApplication {
public static void main(String[] args) {
SpringApplication.run(PermitdemoApplication.class, args);
}
}
3

Create the Permit controller

Create a PermitController class in the same package:

package com.example.permitdemo;

import io.permit.sdk.Permit;
import io.permit.sdk.PermitConfig;
import io.permit.sdk.api.PermitApiError;
import io.permit.sdk.api.PermitContextError;
import io.permit.sdk.enforcement.Resource;
import io.permit.sdk.enforcement.User;
import io.permit.sdk.openapi.models.UserRead;
import io.permit.sdk.openapi.models.UserCreate;
import io.permit.sdk.openapi.models.RoleAssignmentRead;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.Map;

@RestController
public class PermitController {

private final Permit permit;

public PermitController(
@Value("${PERMIT_API_KEY}") String apiKey,
@Value("${PDP_URL:http://localhost:7766}") String pdpUrl
) {
this.permit = new Permit(
new PermitConfig.Builder(apiKey)
.withPdpAddress(pdpUrl)
.withDebugMode(true)
.build()
);
}
}

The constructor creates a Permit client from two properties that Spring resolves from environment variables:

VariableValue
PERMIT_API_KEYYour environment API key from 2. Get your API key. The app fails to start when this variable is missing.
PDP_URLThe PDP address from 3. Run the PDP. The controller uses http://localhost:7766 when this variable is missing.
4

Add the /register and /posts endpoints

Add the following methods inside the PermitController class, before the closing brace:

// Register a user and assign the Reader role
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody Map<String, String> body) {
String email = body.get("email");
String firstName = body.get("first_name");
String lastName = body.get("last_name");

if (email == null || firstName == null || lastName == null) {
return ResponseEntity.badRequest().body(Map.of("error", "Missing required fields"));
}

try {
UserRead user = permit.api.users.sync(
new UserCreate(email)
.withEmail(email)
.withFirstName(firstName)
.withLastName(lastName)
).getResult();
// Assign a role to the user ("Reader" in tenant "default")
RoleAssignmentRead roleAssignment = permit.api.users.assignRole(email, "Reader", "default");
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of(
"message", "User registered and role assigned",
"user", user,
"role_assignment", roleAssignment
));
} catch (IOException | PermitApiError | PermitContextError e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Failed to sync user or assign role"));
}
}

// Check if the user has access to create a post
@PostMapping("/posts")
public ResponseEntity<?> posts(@RequestBody Map<String, Object> body) {
String userKey = (String) body.get("user");
String action = "create";
String resource = "Post";

if (userKey == null) {
return ResponseEntity.badRequest().body(Map.of("error", "Missing required fields"));
}

try {
User user = new User.Builder(userKey).build();
Resource res = new Resource.Builder(resource).build();
boolean permitted = permit.check(user, action, res);
if (permitted) {
return ResponseEntity.ok(Map.of("message", "User is permitted"));
} else {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of("message", "User is not permitted"));
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Map.of("error", "Permission check failed"));
}
}

The controller has two endpoints:

EndpointWhat it does
POST /registerSyncs the user to Permit.io with permit.api.users.sync(), with the email address as the user key, then assigns the Reader role in the default tenant with permit.api.users.assignRole(). Returns 201 with the user and the role assignment.
POST /postsAsks the PDP whether the user key in the user field of the request body can create a Post. Returns "User is permitted", or 403 with "User is not permitted".

Both endpoints return 400 when a required field is missing and 500 when a call to Permit.io or the PDP fails. To protect other endpoints, such as commenting or editing, change the action and resource values.

note

In a production app, take the user key from your authenticated session. This example reads the user key from the request body so that you can test the endpoint with curl.

5

Start the Spring Boot app

Set the environment variables and start the app, replacing <YOUR_API_KEY> with your API key:

export PERMIT_API_KEY=<YOUR_API_KEY>
export PDP_URL=http://localhost:7766
mvn spring-boot:run

The app listens on http://localhost:8080, the Spring Boot default port.

5. Test the permission check

Register two users, give one of them the Author role, and confirm that the PDP allows only that user to create a post.

1

Register two users

In a second terminal, register John and Emma:

curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"email": "john@example.com", "first_name": "John", "last_name": "Doe"}'

curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"email": "emma@example.com", "first_name": "Emma", "last_name": "Den"}'

Each request returns HTTP 201 with "message": "User registered and role assigned", the synced user under user, and the Reader role assignment under role_assignment.

2

Assign John the Author role

Both users have the Reader role, which can't create posts. Give John the Author role in the Permit dashboard:

  1. Open the Directory screen and select john@example.com to open the Edit User panel.
  2. Under Permissions Per Tenant, select the Default Tenant.
  3. In Top Level Access, add the Author role.
  4. Click Save.

Edit User panel in the Permit Directory with Reader and Author roles under Top Level Access for john@example.com

For other ways to assign roles, including the API and SDK, see Sync users.

3

Check that John can create a post and Emma can't

Send a POST /posts request for John:

curl -X POST http://localhost:8080/posts \
-H "Content-Type: application/json" \
-d '{"user": "john@example.com"}'

The PDP allows the request because John has the Author role. The app returns {"message":"User is permitted"}.

Send the same request for Emma:

curl -X POST http://localhost:8080/posts \
-H "Content-Type: application/json" \
-d '{"user": "emma@example.com"}'

The PDP denies the request because Emma has only the Reader role. The app returns HTTP 403 with {"message":"User is not permitted"}.

Each check also appears in the Audit Log screen of the Permit dashboard, with the user, action, resource, and decision.

Next steps