skip to Main Content

I am in the early stages of building my web application. I intend to use Keycloak as the identity provider to secure the backend. On my local machine, I am running both Keycloak and my backend as docker containers but on different networks, since eventually in production, I would like to have the authentication server running Keycloak running separately from the backend e.g account.example.com and api.example.com respectively

Locally, my Keycloak container can be accessed via the base URL http://localhost:8080/auth and the backend via http://test.localhost:8000/

I have created a client in the Keycloak realm whose access type is confidential. I am generating the token using the authorization code grant type.enter image description here

Each REST API endpoint on the backend would therefore verify the token passed to the authorization header and then call the Keycloak server to verify the token before processing the request.

The issue that I am currently experiencing is that the token verification fails with the response

{"error":"invalid_token","error_description":"Token verification failed"}'

After investigation, apparently, it’s because I am calling the Keycloak server from the backend API container. If I generate the token using curl within the backend docker container, the token I receive is being verified fine, but a token generated outside the container is not.

I am using python-keycloak as a wrapper for Keycloak REST API

from keycloak import KeycloakOpenID


self._keycloak = KeycloakOpenID(
            server_url='http://host.docker.internal:8080/auth/',
            realm_name='myrealm',
            client_id='myclient',
            client_secret_key='mysecret,
        )

if "HTTP_AUTHORIZATION" not in request.META:
       
        return JsonResponse(
            {"detail": NotAuthenticated.default_detail},
            status=NotAuthenticated.status_code,
        )

 auth_header = request.META.get("HTTP_AUTHORIZATION").split()
 token = auth_header[1] if len(auth_header) == 2 else auth_header[0]

    
    try:
        self.keycloak.userinfo(token)
    except KeycloakInvalidTokenError as e:
        # print(e)
        return JsonResponse(
            {"detail": AuthenticationFailed.default_detail},
            status=AuthenticationFailed.status_code,
        )

How do I resolve this and have token verification working on my local machine

2

Answers


  1. Problem is with the issuer of the token. Issuer of your token from the postman is http://localhost:8080/..., but backend is configured to accept only issuer http://host.docker.internal:8080/.... It is a best practise to use the same protocol:domain[:port] for IdP (Keycloak in your case) everywhere e.g. https://keycloak.domain.com, otherwise you will have this kind of problems.

    Login or Signup to reply.
  2. Your token is invalid, because the issuer (iss) in the token does not match the issuer that is expected by your backend service.

    Your backend (or an adapter/framework within your backend) will use OIDC discovery protocol to determine the expected issuer. For this, it will call https://keycloak-container-name/auth/realms/<your-realm>/.well-known/openid-configuration. This will return metadata like this:

    {
      "issuer":"https://keycloak-container-name/auth/realms/<your-realm>",
      ...
    }
    

    Keycloak will determine the host part of the issuer (keycloak-container-name in this case) based on the request. So, if your backend queries the discovery endpoint wiht keycloak-container-name from within the docker network, the host part will be your-container-name. Your backend will expect the issuer to be https://keycloak-container-name/auth/realms/<your-realm> in this case.

    Now, if you want to query a token from your frontend, your frontend will send the request to http://localhost:8080/auth/.... Since the issuer will be determined based on the request, the issuer in that token will be https://localhost:8080/auth/realms/<your-realm> in this case.
    This does not match the expected issuer https://keycloak-container-name/auth/realms/<your-realm> and therefore the token will be rejected as invalid.

    You can also verify this by calling the OIDC discovery endpoint via http://localhost:8080/auth/realms/<your-realm>/.well-known/openid-configuration. You will get a response like this:

    {
      "issuer":"http://localhost:8080/auth/realms/<your-realm>",
      ...
    }
    

    To fix this you can set the Frontend URL in your realm to http://localhost:8080/auth. With this setting the issuer will no longer be determined in the request, but will be fixed http://localhost:8080/auth/realms/<your-realm>.
    You can check this from within your backend container by issuing a request to https://keycloak-container-name/auth/realms/<your-realm>/.well-known/openid-configuration. This will return metadata like this now:

    {
      "issuer":"http://localhost:8080/auth/realms/<your-realm>",
      ...
    }
    

    If you do not want to configure this for every realm seperately, you may instead configure the default hostname provider server-wide.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search