Add Context to CoroutineContext from within Ktor plugin handler

Hi all,

I’m trying to create a SecurityContext (an object that contains info about the logged in user) and add it in the CoroutineContext by using a CoroutineContextElement.

I need to do this by creating a RouteScopePlugin during the AuthenticationChecked hook (by using on(AuthenticationChecked). However, I’m not sure how to combine the current coroutine context with the context element that I create. In the base API, I could use the withContext(contextElement) { proceed() } way, but with the new Ktor handler API, it seems like I can’t combine the coroutine contexts.

Or, if you could suggest another way in doing this, that would be appreciated too :slight_smile:

Example Code:

data class SecurityContext(val userId: String)

class RequestContextElement(val context: SecurityContext) : AbstractCoroutineContextElement(Key) {
    companion object Key : CoroutineContext.Key<RequestContextElement>
}

val CoroutineContext.securityContext: SecurityContext
    get() = this[RequestContextElement]?.context ?: throw IllegalStateException("SecurityContext not set in CoroutineContext")

val RequestContextPlugin = createRouteScopedPlugin(name = "RequestContextPlugin") {
    on(AuthenticationChecked) { call ->
        val user = SubjectUtil.getUserFromRequest(call)
        val securityContext = SecurityContext(
            userId = user.id,
        )

//      how to proceed the rest of the execution with this combined context?
        coroutineContext + RequestContextElement(requestInfo)

//      this is how you would do it via the Base API
//        withContext(RequestContextElement(securityContext)) {
//            proceed()
//        }
    }

P.S.: Apologies if this is not the correct forum to post this question. I have posted this on Ktor’s reddit, however I figured it might be wise to bring this question to a broader audience.

1 Like

I think a CoroutineContext is just like a giant map, so couldn’t you do coroutineContext[RequestContextElement.Key] = RequestContextElement(securityContext)

You’re correct in stating that it’s like a map. However, it doesn’t expose a set function. So I’m unable to set the element directly (or perhaps I just haven’t found of a way to do that) without using withContext.

1 Like

I’ve submitted a ticket to Ktor’s team to expose the Authentication Phases, as it was in past versions. You can keep track of the ticket here.

I believe the idiomatic way of passing request scoped state is by storing it in call.attributes, rather than in the Coroutine context

1 Like

Only if you want to use that state in your API layer. But if you want to know the security context (or other kind of context) in lower layers of your application, then using a type of context object approach is required. Because you don’t want to have to pass the ApplicationCall, or the XxxContext, object everywhere.

The approach I’m asking about is a common approach used in other languages and frameworks, i.e. Spring, Go, etc.

1 Like

I think a thin wrapper on the API layer could be useful here then. Simply store the extra context elements in call.attributes, then in the thin wrapper, simply extract the extra context and do withContext with it.