Hi all, could someone tell me how I can intercept ...
# ktor
b
Hi all, could someone tell me how I can intercept after the authentication feature pipeline has been finished? I want to load an user profile after being logged in
anyone?
m
Copy code
routing {
    authenticate("myauth1") {
        get("/authenticated/route1") {
            // load your profile here. 
        }
    }
}
I don't know about a way to intercept after auth. But it wouldn't be performand anyways, as some clients authenticate again prior every request.
b
I made an authorization feature which can be configured with an role resolver (loads roles from an account microservice). The problem is that these get loaded before the token has been validated, so it thinks the user is not logged in yet. As for the performance, it is fine to do this every request for now, will build in a memory cache of some kind later on.
Got the authorization feature working, just want to check if the user is authenticated before fetching the roles. As an example:
Copy code
install(Authorization) {
        roleResolver = { ... }
    }

    routing {
         authenticate {
             authorize("admin) {
                 // admin routes here...
            }
        }
   }
m
Why do you have your roles in the authorization if you don't use them for authorization?
b
I use it for making authorized routes based on the resolved roles, like giving access rights to resources, is that not authorization?
m
Oh, now I understand your problem, but I really don't have any answer to that
Sorry
Just an idea, can't you stack multiple
authenticate
interceptors into each other? You could do an authorization feature for login and one for the roles. I don't know how to get the authenticated user in the second feature, but there should be a way. As I said, just an idea, never tested sth like that.
Copy code
routing {
    authenticate("login") {
        authenticate("adminRole") {
            get("/") {
                // do your privileged stuff
            } 
        }
    }
}
b
The problem is that the roles are not mapped into the token but are stored at an external account microservice, we use the identity server (keycloak) solely for authenticating the user
e
Looking into this a bit, it looks like the authentication feature defines the phase
AuthenticationPipeline.RequestAuthentication
. You may be able to register a new phase after it via the following:
Copy code
val attachUserPhase = PipelinePhase("attachUserFromMicroservice")
pipeline.insertPhaseAfter(AuthenticationPipeline.RequestAuthentication, attachUserPhase)
pipeline.intercept(attachUserPhase) {
  // Look up user profile here and attach to call.attributes
}
You should be able to find more about this via the following resources: Ktor authentication feature docs: https://ktor.io/servers/features/authentication.html Ktor authentication feature source: https://github.com/ktorio/ktor/tree/master/ktor-features/ktor-auth/jvm/src/io/ktor/auth Pipeline machinery docs: https://ktor.io/advanced/pipeline.html
b
Thanks! will have a look at that
v
Hey @bram93 did you managed to solve this? I`m struggling with the same issue
b
I ended up making my own principal data class and doing the account fetching in the validate function:
Copy code
validate { credentials ->
                if (credentials.payload.audience.contains(jwkAudience)) {
                    val account = httpClient.get<Account>(accountEndpoint) {
                        header("Authorization", request.header("Authorization"))
                    }

                    KKPrincipal(credentials.payload, account)
                } else null
            }
Not the most elegant solution but worked for my usecase
then u can retrieve the principal like this:
call.authentication.principal<KKPrincipal>()
v
Alright, I got it. The problem in my case is that everything thrown inside validate respond with Unauthorized, I wish I could add my additional validation responding with Forbidden on fails.
e
Can’t you set the authorization response via the
challenge [ }
block in your provider definition?
v
I tried it, but if I do this way all responses will be Forbidden, I want forbidden only for the permissions check, following API best practices. This is terribly hard to achieve
e
If you’re trying to do validation of the principal you could do a per-route interception with an extension function similar to how I’m attaching a user from a JWT principal in my code:
Forbidden response should really be per-route anyway depending on database data
v
Could you please share an example on how do you apply this route in your routing definition? Is it nested in authenticate's route?
e
You just drop it in your routing block or a route block, i.e.:
Copy code
routing {
  addUserAttributeInterceptor(userService)
}
Or:
Copy code
routing {
  get("/some-authenticated-endpoint") {
    // Now this route and all sub-routes will be intercepted by the interceptor, but not others in the routing block
    addUserAttributeInterceptor(userService)
  }
}
I also have interceptors on specific routes which will verify if referenced data exists, automatically returning a 404 if it doesn’t. Lots of good use-cases for interceptors.
🚀 1
v
That worked just fine! It was right there all this time. Thanks a lot Evan