ktor

How to dynamically choose which authentication method is used in Ktor?


I implemented google sign-in in my application like so:

fun Application.module(testing: Boolean = false) {
  install(CallLogging)

  install(ContentNegotiation) {
    gson {
      setPrettyPrinting()
    }
  }

  val jwtIssuer = environment.config.property("jwt.domain").getString()
  val jwtAudience = environment.config.property("jwt.audience").getString()
  val jwtRealm = environment.config.property("jwt.realm").getString()

  val jwkProvider = JwkProviderBuilder(URL("https://www.googleapis.com/oauth2/v3/certs"))
    .cached(10, 24, TimeUnit.HOURS)
    .rateLimited(10, 1, TimeUnit.MINUTES)
    .build()

  install(Authentication) {
    jwt {
      verifier(jwkProvider) {
        withIssuer(jwtIssuer)
        withAudience(jwtAudience)
      }
      realm = jwtRealm
      validate { credentials ->
        if (credentials.payload.audience.contains(jwtAudience))
          JWTPrincipal(credentials.payload)
        else
          null
      }
    }
  }

  routing {
    authenticate {
      post("/token-sign-in") {
        val payload = call.principal<JWTPrincipal>()?.payload ?: error("JWTPrincipal not found")

        call.respond(
          UserWire(
            id = payload.subject,
            email = payload.getClaim("email").asString(),
            name = payload.getClaim("name").asString(),
            profilePictureUrl = payload.getClaim("picture").asString()
          )
        )
      }
    }
  }
}

I want to authenticate the user every single time they access one of the routes, but I want to have both google and firebase-auth login as an option. The thing is that they require different methods to check the authenticity of the given token, hence I need two authentication methods.

I was thinking of including an "AuthenticationProvider: "Google|Firebase"" in the header of the call, and according to its value, I would decide which authentication method should be called.

So something like this:

fun Application.module(testing: Boolean = false) {
  install(Authentication) {
    jwt("google") {
      // verify google sign in token
    }
    jwt("firebase") {
      // verify firebase token
    }
    firebaseOrGoogle("firebaseOrGoogle") {
      // check header value for auth provider
      // verify token with either "firebase" or "google" auth methods
    }
  }

  routing {
    authenticate("firebaseOrGoogle") {
      post("/token-sign-in") {
        // ...
      }

      get("/transactions") {
        // ...
      }
    }
  }
}

Is this at all possible? If this is possible please could you provide some code as to how to dynamically decide which authentication method should be called?


Solution

  • As an alternative solution, you can configure an authentication feature to try proving the identity of a user by both methods. The first successful check wins. To do that just pass those two configuration names to the authenticate method:

    routing {
        authenticate("google", "firebase") {
            post("/token-sign-in") {
                // ...
            }
    
            get("/transactions") {
                // ...
            }
        }
    }
    

    The order of arguments determines which check comes first.