Scopes configuration based on roles,

Hello,

I am using Keycloak with two React frontends and a backend. The two frontends use two separate clients. The scopes are currently assigned to the clients. This has the drawback that the user for frontend A is able to log in into frontend B, receiving the scopes of client B.

  • Is there a way to configure scopes based on the users role?
  • Is there a recommended way to fix the above situation?

See: Angular, OAuth 2.0 and Keycloak

What a pity.

I came up with the following:

  • the user has a roles assigned
  • the client has a real role mapping, which puts the role into the access token next to the client scopes
  • in the backend I would validate for role from the user + scope from the client

Would this make sense?

My solution is as follows.

In Keycloak I add a User Realm Role Mapper to the client.

This puts the roles of the user into the access token.

Then in Spring I have created an interceptor, which checks the roles in the token against roles defined in an annotation on the controller.

@Component
class RoleCheckInterceptor(private val mapper: ObjectMapper) : HandlerInterceptor {

    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        if (handler !is HandlerMethod) {
            return true
        }
        val rolesFromAnnotation = mutableSetOf<String>()
        handler.bean::class.findAnnotation<AuthorizeRoles>()?.value?.forEach { rolesFromAnnotation.add(it) }
        handler.method.getAnnotation(AuthorizeRoles::class.java)?.value?.forEach { rolesFromAnnotation.add(it) }
        if (rolesFromAnnotation.isEmpty()) {
            return true
        }

        val roles = request.getHeader("Authorization")?.let { headerValue ->
            val jwt = JWTParser.parse(headerValue.split(" ")[1])
            jwt.jwtClaimsSet?.getClaim("roles")?.let { r ->
                val roles = r as JSONArray
                HashSet(roles)
            }
        } ?: emptySet<String>()
        val allowed = rolesFromAnnotation.all { roles.contains(it) }
        return if (allowed) {
            true
        } else {
            val unauthorized = Unauthorized(request.requestURI).toFE()
            response.status = unauthorized.status
            response.setHeader(HttpHeaders.CONTENT_TYPE, problemContentType)
            mapper.writeValue(response.writer, unauthorized)
            response.writer.flush()
            response.writer.close()
            false
        }
    }
}