Stuart
Stuart

Reputation: 191

Optional route authentication in Ktor

I have a route in my Ktor application that I want to optionally authenticate. What is the best way to go about this? If I put two routing blocks in, both calls default to the unauthenticated one.

e.g.

    routing {
        post("/my-route") {
            val request = call.receive<MyRouteRequest>()
            ...
        }
        authenticate(Constants.myAuthScope) {
            post("/my-route") {
                val request = call.receive<MyRouteRequest>()
                val user = call.principal<User>()
                ...
            }
      }

Upvotes: 1

Views: 712

Answers (2)

goss.beta
goss.beta

Reputation: 113

Try authenticate's optional param, like

authenticate(Constants.myAuthScope, optional = true) {
    post("/my-route") {}
}

Upvotes: 1

Rohde Fischer
Rohde Fischer

Reputation: 1482

It should be possible using more explicit models combined with validation of those either in the route or perhaps in the underlying service (depends if this is seen as domain logic or API logic)

For basic auth it looks a bit like:

sealed interface PrincipalResult {
    data class User(/* ... */): PrincipalResult

    object NoUserProvided: PrincipalResult

    // This might be replaced with a null result to conform with the Ktor API
    // I prefer making it explicit and communicate what's going on
    // and not just accept a null that means everything and nothing.
    //
    // This can also be made into a data class and expanded
    // with additional information, allowing for better errors and richer debugging
    object InvalidUserCredentials: PrincipalResult
}
install(Authentication) {
    basic("stuart-auth") {
        realm = "Access to the '/' path"
        validate { credentials ->
            if (credentials.isMissing()) {
                PrincipalResult.NoUserProvided
            } else if (credentials.isValid() {
                PrincipalResult.User(/* ... */)
            } else {
                PrincipalResult.InvalidUserCredentials
            }
        }
    }
}

now one can do:

authenticate(Constants.myAuthScope) {
    post("/my-route") {
        val request = call.receive<MyRouteRequest>()
        val principalResult = call.principal<PrincipalResult>()

        when (principalResult) {
            is PrincipalResult.User ->
            is PrincipalResult.NoUserProvided ->
            is PrincipalResult.InvalidUserCredentials ->
        }
        // ...
    }
}

This pattern should of course be applied to whichever authentication scheme you actually use, such as JWT, OAuth, LDAP etc.

Upvotes: 2

Related Questions