0

I'm using a server that authenticates through keycloak. In keycloak I have created a realm and am able to get an Access token as a response. This access token gets fed now into my Ktor application.

However, I'm not quite sure how to protect routes in an easy manner. I want to have some protected routes that have a authenticate("keycloakOAuth"){} scope around it which handles validating the access token and refreshing using the refresh token if the access token is expired.

Currently I have keycloak inside Ktor configured as this:


        authenticate("keycloakOAuth") {
            get("login") {}

            route("/callback") {
                // This handler will be executed after making a request to a provider's token URL.
                handle {
                    val principal = call.authentication.principal<OAuthAccessTokenResponse>()

                    if (principal != null) {
                        val response = principal as OAuthAccessTokenResponse.OAuth2
                        call.respondText { "Access token: ${response.accessToken}" }
                    } else {
                        call.respondText { "NO principal" }
                    }
                }
            }
        }

This works fine because when I go to login I'm getting sent to the Keycloak login page and I can login. When I logged in the callback executes and I get my Access Token back.

When I'm trying to protect routes however, some odd stuff happens. I know that I need to validate the incoming JWT token. But I have no clue how to given the Ktor capabilities. The examples are also of little help, since they are quite vague.

Currently I have something like this:


        authenticate("keycloakOAuth") {
            get("/testAuth") {
                val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
                if(principal != null) {
                    call.respondText("Authenticated!")
                } else {
                    call.respondText("Unauthenticated...")
                }
            }
        }

But my application will always send me to the login page and then callback page, even though I am sending the Bearer token when I'm testing this call.

My question is:

How do I protect routes in a manner that they need a valid token, with the same syntax that Ktor uses (like authenticate(){}). Do I need to configure JWT for this?

Danoctum
  • 321
  • 5
  • 16

1 Answers1

1

When you request one of the routes under authenticate, the full cycle of OAuth authentication is triggered. This is because the Authentication plugin is designed so a client sends credentials and gets authenticated for each request. For some reason, OAuth integration was implemented on top of the Authentication plugin hence such unexpected behavior.

To solve your problem you can have only /login and /callback routes restricted. In the callback's handler save user ID and tokens in a session or in any other storage for future use. For other routes, you can check manually the fact that a user is authenticated and then use tokens from storage to acquire protected data from the resource server. For convenience, you can create some extension functions to minimize the amount of boilerplate code. Unfortunately, there is no built-in functionality to make it work out of the box.

You don't need to configure JWT for this.

Aleksei Tirman
  • 4,658
  • 1
  • 5
  • 24
  • Thanks for the detailed answer. I'll be using sessions from now on to get the user's information. However, in terms of validating the token. How can I do this? I see [here](https://stackoverflow.com/a/59331971/9372943) that they use verifyOffline with a certificate. But I'd like to know how to do this in the Java version I've found [this](https://javadoc.io/doc/org.keycloak/keycloak-core/latest/org/keycloak/TokenVerifier.html) thus far. – Danoctum Sep 08 '21 at 12:52
  • I suggest reading an article https://ktor.io/docs/jwt.html about JWT in Ktor's documentation. – Aleksei Tirman Sep 08 '21 at 16:32