Firebase and Spring Boot Based Role Management and Authorization

In the previous post we configured spring boot security with firebase to seamlessly authenticate rest api ( https://thepro.io/post/firebase-authentication-for-spring-boot-rest-api-5V ) .

In this post, we will restrict access to resources based on firebase roles.

Photo credit – seabass creatives @ unsplash.com

We are going to utilise firebase’s claims object inside UserRecord class to store roles, which is its actual purpose as per firebase documentation.

To manage roles in spring boot server we will,

  1. Define Roles
  2. Add Roles
  3. Verify Roles
  4. Remove Roles

Define Roles

First define roles as per your requirement. It is a must for roles to start with ROLE_ for the spring security to identify them in authorization flow.

On top of this, optionally annotations can be create per role for easy of use. We will look into annotation creation in “Verify Roles” section.

// RoleConstants
// ...
public class RoleConstants {
	public static final String ROLE_SUPER = "ROLE_SUPER";

	public static final String ROLE_ADMIN = "ROLE_ADMIN";

	public static final String ROLE_SELLER = "ROLE_SELLER";

}

Add Roles

Now, to add roles to a user,

  1. Get UserRecord from FirebaseAuth Object
  2. Check if User already has the role defined
  3. Check if the role is valid application role with
  4. securityProps.getValidApplicationRoles().contains(role)
  5. Add role to custom claim map Object and set Claim
// RoleServiceImpl
// ...

@Override
	public void addRole(String uid, String role) throws Exception {
		try {
			UserRecord user = firebaseAuth.getUser(uid);
			Map<String, Object> claims = new HashMap<>();
			user.getCustomClaims().forEach((k, v) -> claims.put(k, v));
			if (securityProps.getValidApplicationRoles().contains(role)) {
				if (!claims.containsKey(role)) {
					claims.put(role.toLowerCase(), true);
				}
				firebaseAuth.setCustomUserClaims(uid, claims);
			} else {
				throw new Exception("Not a valid Application role, Allowed roles => "
						+ securityProps.getValidApplicationRoles().toString());
			}

		} catch (FirebaseAuthException e) {
			log.error("Firebase Auth Error ", e);
		}

	}

Verify Roles

Next,

Get claim from FirebaseToken object in token/session verification session and inject the role in claim into spring security context using UsernamePasswordAuthenticationToken object.

// SecurityFilter
// ...

// Handle roles
			decodedToken.getClaims().forEach((k, v) -> authorities.add(new SimpleGrantedAuthority(k)));
			
// Set security context
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, new Credentials(type, decodedToken, token, sessionCookieValue), authorities);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);

With roles available in security context, you can use one of several options available to restrict access to resources using roles. Annotations like @Secured, @RolesAllowed, @PreAuthorize, @PostAuthorized can be applied.

Custom Role Annotation

For the sake of simplicity and ease of use, I personally like to define Per role annotations with preauthorization feature.

//IsSeller
// ...

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('SELLER')")
public @interface IsSeller {

}

This enables you to simply add @IsSeller annotation to restrict users with role seller enabled.

// SellerController
// ...

@RestController
@RequestMapping("seller")
public class SellerController {
	
	@GetMapping("data")
	@IsSeller
	public String getProtectedData() {
		return "You have accessed seller only data from spring boot";
	}
	
}

Remove roles

To remove roles, remove the role from UserRecord and update custom claims.

@Override
	public void removeRole(String uid, String role) {
		try {
			UserRecord user = firebaseAuth.getUser(uid);
			Map<String, Object> claims = new HashMap<>();
			user.getCustomClaims().forEach((k, v) -> claims.put(k, v));
			if (claims.containsKey(role)) {
				claims.remove(role);
			}
			firebaseAuth.setCustomUserClaims(uid, claims);
		} catch (FirebaseAuthException e) {
			log.error("Firebase Auth Error ", e);
		}
	}

That’s it ! Happy Coding !

Source Code

https://github.com/gladius/firebase-spring-boot-rest-api-authentication

Leave a star on Github if this helped !