JWT認証・JWK認証

Estimated reading time: 1 minute

KtorはJWT (JSON Web Tokens)をサポートしています。 JWTはエンコードされたJSONペイロードによって認証する仕組みです。 ステートレスな認証が必要なAPIをスタンダードな方法で作る上で役に立ち、たくさんの言語によってJWTをサポートするクライアントライブラリもあります。

この機能は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はCredentialまたはPrincipalとしてJWTペイロードを利用するクラスをいくつか持ちます。

class JWTCredential(val payload: Payload) : Credential
class JWTPrincipal(val payload: Payload) : Principal

server/routeの設定:

JWTとJWKはそれぞれ少しずつ違ったパラメータのメソッドを持ちます。 ともにrealmパラメータを必要とし、それはWWW-Authenticateレスポンスヘッダーで使われます。

verifierとvalidatorの使用:

verifierはsecretを署名を検証しソースを信頼するために利用します。 諸々正しいことを検証しPrincipalを生成するために、validateコールバック内でpayloadのチェックも行うことができます。

application.conf:

jwt {
    domain = "https://jwt-provider-domain/"
    audience = "jwt-audience"
    realm = "ktor sample app"
}

JWT auth:

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

install(Authentication) {
    jwt {
        realm = jwtRealm
        verifier(makeJwtVerifier(jwtIssuer, jwtIssuer))
        validate { credential ->
            if (credential.payload.audience.contains(jwtAudience)) JWTPrincipal(credential.payload) else null
        }
    }
}

private val algorithm = Algorithm.HMAC256("secret")
private fun makeJwtVerifier(issuer: String, audience: String): JWTVerifier = JWT
        .require(algorithm)
        .withAudience(audience)
        .withIssuer(issuer)
        .build()

JWKプロバイダの利用:

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