Skip to content

Security documentation not being generated #64

@sigmanil

Description

@sigmanil

We've been able to implement JWT-authenticated endpoints through the Ktor-OpenAPI-Generator API, and authentication works - but the generated OpenAPI.json doesn't contain any information about the security scheme. It should, right?

We have written the following code to tell Ktor-OpenAPI-Generator about the authentication:

  val authProvider = JwtProvider();

  inline fun NormalOpenAPIRoute.auth(route: OpenAPIAuthenticatedRoute<UserPrincipal>.() -> Unit): 
  OpenAPIAuthenticatedRoute<UserPrincipal> {
    val authenticatedKtorRoute = this.ktorRoute.authenticate { }
    var openAPIAuthenticatedRoute= OpenAPIAuthenticatedRoute(authenticatedKtorRoute, this.provider.child(), authProvider = authProvider);
    return openAPIAuthenticatedRoute.apply {
        route()
    }
  }

  data class UserPrincipal(val userId: String, val email: String?, val name: String?) : Principal

  class JwtProvider : AuthProvider<UserPrincipal> {
    override val security: Iterable<Iterable<Security<*>>> =
        listOf(listOf(Security(SecuritySchemeModel(SecuritySchemeType.openIdConnect, scheme = HttpSecurityScheme.bearer, bearerFormat = "JWT", name = "JWT"), Scopes.values().toList())))

    override suspend fun getAuth(pipeline: PipelineContext<Unit, ApplicationCall>): UserPrincipal {
        return pipeline.context.authentication.principal() ?: throw RuntimeException("No JWTPrincipal")
    }

    override fun apply(route: NormalOpenAPIRoute): OpenAPIAuthenticatedRoute<UserPrincipal> {
        val authenticatedKtorRoute = route.ktorRoute.authenticate { }
        return OpenAPIAuthenticatedRoute(authenticatedKtorRoute, route.provider.child(), this)
    }
  }

  enum class Scopes(override val description: String) : Described {
    Profile("Some scope")
  }

In our Application.apirouting, we wrap the required paths with auth{}, and make sure we use the routing functions from com.papsign.ktor.openapigen.route.path.auth.* in those cases. We are able to access the authenticated user through the principal()-function. An example:

auth {
  route("/user") {
        get<Unit, List<User>, UserPrincipal> {
            val principal = principal()
            log.info("principal1 ${principal.userId}")
            log.info("principal1 ${principal.email}")
            respond(registry.userRepository.retrieveAllUsers())
        }
  }
}

Everything seems to work fine for actual use - but as mentioned, we're not finding any information about the security scheme in the generated openapi.json. :-(

We are calling addModules(authProvider) as part of the installation of OpenAPIGen, but we're under the impression this isn't working as intended. (We are supposed to add it as a module, right?) Here, we're ending up in the object com.papsign.ktor.openapigen.modules.handlers.AuthHandler:

package com.papsign.ktor.openapigen.modules.handlers

import com.papsign.ktor.openapigen.OpenAPIGen
import com.papsign.ktor.openapigen.model.operation.OperationModel
import com.papsign.ktor.openapigen.model.security.SecurityModel
import com.papsign.ktor.openapigen.modules.ModuleProvider
import com.papsign.ktor.openapigen.modules.ofType
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
import com.papsign.ktor.openapigen.modules.providers.AuthProvider

object AuthHandler: OperationModule {
    override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) {
        val authHandlers = provider.ofType<AuthProvider<*>>()
        val security = authHandlers.flatMap { it.security }.distinct()
        operation.security = security.map { SecurityModel().also { sec ->
            it.forEach { sec[it.scheme.name] = it.requirements }
        } }
        apiGen.api.components.securitySchemes.putAll(security.flatMap { it.map { it.scheme } }.associateBy { it.name })
    }
}

Here, in the configure method, provider seems to contain our authProvider object, but it isn't returned from the "provider.ofType<AuthProvider<*>>()" call. But at this point we're a bit uncertain if we're barking up the wrong tree, and if we're not, why our AuthProvider isn't recognized as one by ofType...

We're not sure if this is a bug in OpenAPIGen or just something we haven't understood about how to use the framework. Any help would be greatly appreciated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions