Skip to content

Commit

Permalink
Allow having multiple authentication alternatives in Ktor controllers (
Browse files Browse the repository at this point in the history
…#321)

* Allow having multiple authentication alternatives in Ktor controllers

`authenticate()` accepts a vararg and by default allows the API access if at least one
option is satisfied.

* fixup! Allow having multiple authentication alternatives in Ktor controllers
  • Loading branch information
lunakoly authored Oct 7, 2024
1 parent 225a52c commit 42f621d
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import io.ktor.client.request.header
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.jackson.jackson
import io.ktor.server.application.ApplicationCall
import io.ktor.server.auth.Authentication
import io.ktor.server.auth.Principal
import io.ktor.server.auth.UserIdPrincipal
import io.ktor.server.auth.basic
import io.ktor.server.auth.*
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.statuspages.StatusPages
import io.ktor.server.response.respond
Expand Down Expand Up @@ -212,6 +209,12 @@ class KtorAuthenticationTest {
UserIdPrincipal("defaultAuth") // always authenticate
}
}

bearer("BearerAuth") {
authenticate {
UserIdPrincipal("defaultAuth") // always authenticate
}
}
}

install(ContentNegotiation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,18 @@ class KtorControllerInterfaceGenerator(

val addAuth = securityOption.allowsAuthenticated && options.contains(ControllerCodeGenOptionType.AUTHENTICATION)
if (addAuth) {
// Using the key from the first security requirement as the auth name
val authName = if (operation.hasSecurityRequirements()) {
operation.securityRequirements.first().requirements.keys.first()
val authNames = if (operation.hasSecurityRequirements()) {
operation.securityRequirements
.filter { it.requirements.isNotEmpty() }
.joinToString(", ") { "\"" + it.requirements.keys.first() + "\"" }
} else {
// Fall back to the global security requirements
this.api.openApi3.securityRequirements.first().requirements.keys.first()
"\"" + this.api.openApi3.securityRequirements.first().requirements.keys.first() + "\""
}

builder
.addStatement(
"%M(\"$authName\", optional = %L) {",
"%M($authNames, optional = %L) {",
MemberName("io.ktor.server.auth", "authenticate"),
securityOption == SecuritySupport.AUTHENTICATION_OPTIONAL,
)
Expand Down
4 changes: 4 additions & 0 deletions src/test/resources/examples/authentication/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ paths:
operationId: testPath
security:
- BasicAuth: [ ]
- BearerAuth: [ ]
parameters:
- in: query
name: testString
Expand Down Expand Up @@ -78,5 +79,8 @@ components:
BasicAuth:
type: http
scheme: basic
BearerAuth:
type: http
scheme: bearer
security:
- basicAuth: [ ]
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface RequiredController {
* - GET /required
*/
public fun Route.requiredRoutes(controller: RequiredController) {
authenticate("BasicAuth", optional = false) {
authenticate("BasicAuth", "BearerAuth", optional = false) {
`get`("/required") {
val principal = call.principal<Principal>() ?: throw
IllegalStateException("Principal not found")
Expand Down

0 comments on commit 42f621d

Please sign in to comment.