Ktor-OpenAPI-Generator icon indicating copy to clipboard operation
Ktor-OpenAPI-Generator copied to clipboard

JWT authorization

Open AndreyZS opened this issue 4 years ago • 7 comments

Good afternoon, please help me implement JWT authorization. Old authorization doesn't work for apiRouting.

An implementation that works in Route

install(Authentication) {
        jwt {
            verifier(JWTService.verifier)
            validate { credential ->
                JWTPrincipal(credential.payload)
            }
        }
    }

And the implementation, unfortunately, which is written in link is not clear to me. Please tell me how to implement ?

AndreyZS avatar Mar 09 '21 11:03 AndreyZS

The basic ktor implementation is not strongly typed and this lib requires strongly typed routes due to the init time analysis of the routes. You need to implement the auth handler class like in the link, there is no way around it and i don't know how to make it clearer than in link. There could be an out of the box implementation for jwt but there isn't one yet. Maybe ask the community if they are willing to make a pull request for that, i heard a few falk about using jwt recently in PRs.

Wicpar avatar Mar 09 '21 20:03 Wicpar

We've managed to get JWT authentication working. I don't really have time to clean it up right now, so this will contain a lot of references to our system, but in case it helps:

First, this function is registered with Ktor:

fun jwt(provider: Authentication.Configuration) {
        provider.apply {
            jwt(name = "user") {
                realm = authSettings.jwtRealm
                verifier(getJwkProvider(authSettings.jwtEndpoint), authSettings.jwtIssuer)
                validate { credentials ->
                    UserPrincipal( //UserPrincipal is a data class inheriting from io.ktor.auth.Principal
                        userId = credentials.payload.subject,
                        email = credentials.payload.claims[ClaimTypes.EMAIL]?.asString(),
                        name = credentials.payload.claims[ClaimTypes.NAME]?.asString(),
                        roles = listOf()
                    )
                }
            }
        }
    }

private fun getJwkProvider(jwkEndpoint: String): JwkProvider {
        return JwkProviderBuilder(URL(jwkEndpoint))
            .cached(10, 24, TimeUnit.HOURS)
            .rateLimited(10, 1, TimeUnit.MINUTES)
            .build()
    }

Then this code is basically what we needed:

val authProvider = JwtProvider()

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

data class UserPrincipal(
    val userId: String,
    val email: String?,
    val name: String?,
    val roles: List<String>
) : Principal

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

    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

sigmanil avatar Apr 12 '21 07:04 sigmanil

I forgot one thing - in addition to the above, we also needed this workaround: https://github.com/papsign/Ktor-OpenAPI-Generator/issues/98

sigmanil avatar May 24 '21 14:05 sigmanil

Any plans to put a generic JWTAuthProvider into this library? Help needed?

Burtan avatar May 30 '21 08:05 Burtan

Any plans to put a generic JWTAuthProvider into this library? Help needed?

If you want to do a PR i will gladly accept it.

Wicpar avatar May 30 '21 11:05 Wicpar

If I find a good way to make sigmanils example generic, I'll make a PR.

Burtan avatar May 30 '21 12:05 Burtan

@Wicpar I created a new provider as you mention, but how can i show in swagger-ui? Here is my apiRouting: `apiRouting { tag(object : APITag { override val description: String get() = "User Methods" override val name: String get() = "User"

    }) {
        val jwtAuthProvider = JwtProvider()
        auth(jwtAuthProvider) {
            route("/user") {
                get<AuthHeader, List<User>>(
                    info(
                        summary = "Get all users",
                        description = "Return a list of all users"
                    ),
                    status(HttpStatusCode.OK),
                    example = listOf(User(1, "emir", "1234"), User(2, "emirhan", "5678")),
                ) {
                    respond(listOf(User(1, "emir", "1234"), User(2, "emirhan", "5678")))
                }
            }
        }
    }
}`

emirhanemmez avatar Dec 22 '21 07:12 emirhanemmez