keycloakaccess-tokenhttp-status-code-401

Keycloak Account API returns Unauthorized 401 with token from user logged in via my spring client


I think the client I added for spring security is missing something that the built-in admin client uses when you visit the http://0.0.0.0:8080/realms/testrealm/account/ manually and login - chrome dev tools shows the same type of get call working just fine with that bearer token. Rather than use a service account to call the admin API, I'd rather try to use the account API with the token I already have for the logged in user. This seems like better principle of least security since I only need to get/update current user's profile info.

What is the key difference between my spring boot app's client using spring security standard Authorization Code Flow to store the logged in user's token in the security context and however the built-in keycloak "account" client which seems to also have the same standard auth code flow which lets the users login, but it's tokens don't get 401 and mine do..??

If it helps, here's the code I'm using to grab the token and make the account GET call with it, but I think the issue is somehow with my realm / client config. I tried copying the role 'view-profile' but I don't see how that matters - looks like just a string label.

What am I missing? This is keycloak 24.0.0 running locally.

import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.spi.ResteasyClientProvider;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Service;
import org.jboss.resteasy.client.jaxrs.*;


@Service
public class KeycloakUserService {

    @Value("${keycloak.auth-server-url}")
    private String serverUrl;
    @Value("${keycloak.realm}")
    private String realm;

    public UserRepresentation getCurrentUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        OidcUser oidcUser = (OidcUser) authentication.getPrincipal();
        String accessToken = oidcUser.getIdToken().getTokenValue();
        String userId = oidcUser.getSubject();
        String url = serverUrl + "/realms/{realm}/account/";

        ResteasyClientProvider clientProvider = Keycloak.getClientProvider();
        ResteasyClient client = (ResteasyClient) clientProvider.newRestEasyClient(null, null, false);
        ResteasyWebTarget target = client.target(url).resolveTemplate("realm", realm);

        System.out.println("URL: " + url);

        Response response = target.request(MediaType.APPLICATION_JSON)
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken)
                .property("userProfileMetadata","true")
                .get();

        if (response.getStatus() == 200) {
            return response.readEntity(UserRepresentation.class);
        } else {
            // Handle errors appropriately
            System.out.println("Error: " + response.getStatus());
            String errorMessage = response.readEntity(String.class);
            System.out.println("Error Message: " + errorMessage);
            return null;
        }

    }

Solution

  • What is the key difference between my spring boot app's client ... and the built-in keycloak "account" client ...??

    Clients roles. Go to clients details and open the Roles tab for the two.