Search code examples
spring-bootoauth-2.0keycloakspring-security-oauth2

Keycloak authenticated client of one application couldn't access another client application in same realm


I am using Keycloak as Identity provider. I have a springboot-mvc-client application which is registered as a confidential client and configured with authorization_code grant type. When I access the application I am redirected to Keycloak login page and after successful login I can see my springboot-mvc-client page. Everything is working as expected.

Now I have another springboot-rest-api which is a ResourceServer. I have configured application.properties as follows:

server.port=8182
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8181/auth/realms/my-realm

Now I want to access springboot-rest-api endpoints from my springboot-mvc-client. I captured the AccessToken and pass as Authorization header with RestTemplate and it is also working fine.

Problem: If I register my springboot-rest-api application as a client with confidential access type in keycloak and try to access API from springboot-mvc-client then API call is failing and response is HTML content of Login page.

My understanding (which could be wrong) is as both clients are registered in same realm I should be able to call springboot-rest-api endpoints with authentication token got from springboot-mvc-client authentication.

Additional observation is if I change springboot-rest-api access type from confidential to bearer-only then it is working fine. Also, I am able to get accessToken for springboot-rest-api using client_credentials grant and using that I am able to call springboot-rest-api endpoints.

The question is: Using one confidential client's access token can't we access another confidential client's resources even if they are in same realm?

PS: Update 1:

With OAuth Security Configuration added to springboot-rest-api it is working fine. ie, By using the same AccessToken obtained from springboot-mvc-client I am able to call springboot-rest-api endpoints.

I have added the following configuration to my springboot-rest-api:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .cors()
                .and()
                .csrf()
                .disable()
                .oauth2ResourceServer()
                .jwt();
    }
}

Update2: Just sharing another observation.

When springboot-rest-api is configured to be just a ResourceServer with spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8181/auth/realms/my-realm in it's application.properties file then it is not required to have above mentioned SecurityConfig configuration. But if I want to make springboot-rest-api act as both OAuth Client + ResourceServer then I need to configure SecurityConfig as mentioned above.


Solution

  • In a nutshell. OAuth2 defines interactions between a Client (requesting authorized access to a protected resource) and a Resource Server (hosting the protected resource). The access is granted via an Access Token. There's no interaction from a Client to another Client.


    I think it helps reasoning in terms of OAuth2 roles rather than applications because there isn't necessarily a 1-to-1 mapping.

    From the OAuth 2.1 specs:

    • Client. An application making protected resource requests on behalf of the resource owner and with its authorization.
    • Resource Server. The server hosting the protected resources, capable of accepting and responding to protected resource requests using Access Tokens.

    Let's consider the OAuth2 Authorization Code Flow first.

    OAuth2 Clients are authorized by a user to access a protected resource on their behalf, hosted by an OAuth2 Resource Server. The access is granted via an Access Token. If springboot-mvc-client is the OAuth2 Client and requires access to springboot-rest-api on behalf of the user, then springboot-rest-api would be the OAuth2 Resource Server according to the OAuth2 Authorization Code Flow.

    OAuth2 Clients are not the recipients of Access Tokens. They get them as-is from the OAuth2 Authorization Server and forward them to the OAuth2 Resource Server to get authorized access to a protected resource. OAuth2 Resource Servers are the recipients of those Access Tokens and they're responsible to read them and validate them. Therefore, an OAuth2 Client wouldn't use such an Access Token to get authorized access to another OAuth2 Client, because the latter wouldn't read an Access Token nor validate its signature. That's in terms of pure OAuth2 roles.


    If springboot-rest-api requires a user to be authenticated and performs requests on behalf of that users, then we're talking about the OAuth2 Client role. But in the previous scenario, it already plays the role of an OAuth2 Resource Server. Therefore, it would have to play both roles (OAuth2 Client when a user calls directly, OAuth2 Resource Server when springboot-mvc-client calls on behalf of a user with an Access Token). At that point, I would probably consider whether it makes sense to introduce some sort of edge service as the entry point to the system (for example, using Spring Cloud Gateway), so that it's the edge service playing the OAuth2 Client role and authenticating users, whereas the other applications within the system would be OAuth2 Resource Server.

    That is valid if we talk about authorized access on the user's behalf. If you're looking for a solution where springboot-mvc-client calls springboot-rest-api on its behalf (not a user), then the OAuth2 Client Credentials should be used instead and there wouldn't be any browser-based login flow. It's a flow that is usually adopted for service-to-service communication (no user involved). We would still talk in terms of OAuth2 Client and OAuth2 Resource Server though. There's a nice article about that on the Okta blog.


    There's no flow defined in the OAuth2 framework between two OAuth2 Clients because what defines an OAuth2 Client is the fact that it wants to make requests for protected resources, which are by definition hosted on a OAuth2 Resource Server.

    However, you can have two applications where the first one plays the role of an OAuth2 Client, and the second one plays the roles of both OAuth2 Client and OAuth2 Resource Server, depending on the interaction is involved with.


    As a final note, you might find the following useful regarding the client types supported by Keycloak (from the docs).

    • confidential. Confidential access type is for server-side clients that need to perform a browser login and require a client secret when they turn an access code into an access token. [...] This type should be used for server-side applications.

    • public Public access type is for client-side clients that need to perform a browser login. With a client-side application there is no way to keep a secret safe. Instead it is very important to restrict access by configuring correct redirect URIs for the client.

    • bearer-only. Bearer-only access type means that the application only allows bearer token requests. If this is turned on, this application cannot participate in browser logins.