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?
Robinyo
December 21, 2020, 8:22pm
#2
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
}
}
}