Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

JWT authorization #93

Open
AndreyZS opened this issue Mar 9, 2021 · 7 comments
Open

JWT authorization #93

AndreyZS opened this issue Mar 9, 2021 · 7 comments

Comments

@AndreyZS
Copy link

AndreyZS commented Mar 9, 2021

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 ?

@Wicpar
Copy link
Collaborator

Wicpar commented Mar 9, 2021

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.

@sigmanil
Copy link

sigmanil commented Apr 12, 2021

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
Copy link

I forgot one thing - in addition to the above, we also needed this workaround: #98

@Burtan
Copy link

Burtan commented May 30, 2021

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

@Wicpar
Copy link
Collaborator

Wicpar commented May 30, 2021

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

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

@Burtan
Copy link

Burtan commented May 30, 2021

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

@emirhanemmez
Copy link

@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")))
                }
            }
        }
    }
}`

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants