JWT and JWK authentication

Ktor supports JWT (JSON Web Tokens), which is a mechanism for authenticating JSON-encoded payloads.It is useful to create stateless authenticated APIs in the standard way, since there are client libraries for itin a myriad of languages.

This feature will handle Authorization: Bearer <JWT-TOKEN>.

This feature is defined in the method io.ktor.auth.jwt.jwt in the artifact io.ktor:ktor-auth-jwt:$ktor_version.

dependencies { implementation "io.ktor:ktor-auth-jwt:$ktor_version"}

dependencies { implementation("io.ktor:ktor-auth-jwt:$ktor_version")}

<project> … <dependencies> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-auth-jwt</artifactId> <version>${ktor.version}</version> <scope>compile</scope> </dependency> </dependencies></project>

Ktor has a couple of classes to use the JWT Payload as Credential or as Principal.

  1. class JWTCredential(val payload: Payload) : Credential
  2. class JWTPrincipal(val payload: Payload) : Principal

Configuring server/routes:

JWT and JWK each have their own method with slightly different parameters. Both require the realm parameter, which is used in the WWW-Authenticate response header.

Using a verifier and a validator:

The verifier will use the secret to verify the signature to trust the source.You can also check the payload within validate callback to ensure everything is right and to produce a Principal.

application.conf:

  1. jwt {
  2. domain = "https://jwt-provider-domain/"
  3. audience = "jwt-audience"
  4. realm = "ktor sample app"
  5. }

JWT auth:

  1. val jwtIssuer = environment.config.property("jwt.domain").getString()
  2. val jwtAudience = environment.config.property("jwt.audience").getString()
  3. val jwtRealm = environment.config.property("jwt.realm").getString()
  4. install(Authentication) {
  5. jwt {
  6. realm = jwtRealm
  7. verifier(makeJwtVerifier(jwtIssuer), jwtIssuer)
  8. validate { credential ->
  9. if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null
  10. }
  11. }
  12. }

Using a JWK provider:

  1. fun AuthenticationPipeline.jwtAuthentication(jwkProvider: JwkProvider, issuer: String, realm: String, validate: (JWTCredential) -> Principal?)
  1. val jwkIssuer = "https://jwt-provider-domain/"
  2. val jwkRealm = "ktor jwt auth test"
  3. val jwkProvider = JwkProviderBuilder(jwkIssuer)
  4. .cached(10, 24, TimeUnit.HOURS)
  5. .rateLimited(10, 1, TimeUnit.MINUTES)
  6. .build()
  7. install(Authentication) {
  8. jwt {
  9. verifier(jwkProvider, jwkIssuer)
  10. realm = jwkRealm
  11. validate { credentials ->
  12. if (credentials.payload.audience.contains(audience)) JWTPrincipal(credentials.payload) else null
  13. }
  14. }
  15. }