it’s a bit annoying though that it has to be inser...
# ktor
l
it’s a bit annoying though that it has to be inserted in this way, since the AuthenticatePhase only exists within routes of the application that are authenticated, so you end up repeating yourself
h
Hi, may be you can try something like `
Copy code
val phase = PipelinePhase("doSomeLogging")
pipeline.insertPhaseAfter(ApplicationCallPipeline.Features,phase)
` With a phase linked after Features, I guess Authentication is done so you can have all you need ? Feel free to have a look at documentation for more details pipeline phase sequence (https://ktor.io/advanced/pipeline.html#ApplicationCallPipeline)
l
the authentication phase does the same- this does not give the desired behavior
that is, the authentication phase also binds to just after Features
so the authenticated principal is not present when you try to get it with `
Copy code
call.authentication.principal<Principal>()
h
Try
val phase = PipelinePhase("doSomeLogging")    pipeline.insertPhaseBefore(ApplicationCallPipeline.Call,phase)
May be I don’t understand your point, sorry 😞
l
this also did not seem to produce the desired behavior
h
What I did myself, it’s to use a method around my routes such as : `
Copy code
fun Route.routeEnrichedWithLogData(callback: Route.() -> Unit): Route =
    routeWithAction(callback) {
        proceed()
        MDC.put("principal", this.principal<UserIdPrincipal>()?.name ?: "unknown")
    }
` This way I can log my Principal.
The process() call do the job and after I put all my logging.
Copy code
install(Routing) {
    routeEnrichedWithLogData {
                val registry = feature(DropwizardMetrics).registry
                healthEndPoints()
                storageHealthEndPoints()
                metricsEndPoints(registry)
...
Does it fit to your need ?
l
that’s essentially what i’ve got, yeah
thanks!
👍 1
r
Found this thread after searching how to add info from an auth principal into the MDC, and ran into the exact same issue and ended up unfortunately solving it similarly. in 2025, CallLogging is an application plugin and the authentication interceptor is a route scoped plugin that will always take precedence (this was't super obvious to me, i thought they would all get merged together into one big pipeline, but i can understand why that might not make sense too). I ended up solving it with the following:
Copy code
object AfterAuthMDCHook : Hook<suspend (ApplicationCall, suspend () -> Unit) -> Unit> {
    private val AfterAuthMDCPhase = PipelinePhase("AfterAuthMDCPhase")

    override fun install(
        pipeline: ApplicationCallPipeline,
        handler: suspend (ApplicationCall, suspend () -> Unit) -> Unit,
    ) {
        // AfterAuth phase is inserted after Plugins, before Call should always be after the AfterAuth
        pipeline.insertPhaseBefore(ApplicationCallPipeline.Call, AfterAuthMDCPhase)
        pipeline.intercept(AfterAuthMDCPhase) {
            handler(call, ::proceed)
        }
    }
}

val LogAuthPrincipal = createRouteScopedPlugin("LogAuthPrincipal") {
    on(AfterAuthMDCHook) { call, proceed ->
        call.principal<User>?.id?.let { userId ->
            withLoggingContextAsync("userId" to userId) { proceed() }
        } ?: proceed()
    }
}

fun Route.authenticateWithLogContext(
    vararg configurations: String? = arrayOf(null),
    optional: Boolean = false,
    build: Route.() -> Unit,
): Route = authenticate(configurations = configurations, optional = optional) {
    install(LogAuthPrincipal)
    build()
}
Then within my route, instead of calling
authenticate("configuration")
, i'd call
authenticateWithLogContext("configuration")