KtorでOAuthプラグインを利用する方法
Ktorのv.2.0.0が正式にリリースされそうなので本格的に触ってみようと思い、このところKtor Serverの調査などをしていました。(実際、4/11にリリースされました🎉)
その際に、KtorのOAuth周りは調べただけではイメージがわかなかったので、一度手を動かして実装してみました。
そして実装してみたところ、意外と詰まる部分があったので自分の備忘録も兼ねて記事にしました。
前提
記事内に出てこない開発環境は以下の通りです。
Gradle:7.1.1
Kotlin:1.6.20
JVM:17.0.1 (Amazon.com Inc. 17.0.1+12-LTS)
Ktor:2.0.0
ちなみにKtorはKtor Project Generatorでプラグインの追加なしで作成している前提になります。
OAuthの下準備
今回の実装ではGoogleのOAuthを利用するので、事前にGoogle Cloud Platformから「OAuth 同意画面」と「認証情報」を設定し、クライアントIDとクライアントシークレットを取得しておきます。
また自分の場合は.env
を作成し、環境変数にクライアントIDとクライアントシークレットを設定して、direnvで読み込んでいます。
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
必要なKtorプラグインの導入
まずは必要なKtorプラグインをbuild.gradle.kts
に追加します。
dependencies {
implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.ktor:ktor-server-sessions:$ktor_version")
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-apache:$ktor_version")
}
GoogleのOAuth 2.0の利用
まずは追加でplugins
配下にSecurity.kt
を作成します。
以下のようにコードを記述することで、GoogleのOAuth 2.0を利用しています。
fun Application.configureSecurity() {
install(Sessions) {
cookie<UserSession>("user_session")
}
install(Authentication) {
oauth("auth-oauth-google") {
urlProvider = { "http://localhost:8080/callback" }
providerLookup = {
OAuthServerSettings.OAuth2ServerSettings(
name = "google",
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
requestMethod = HttpMethod.Post,
clientId = System.getenv("GOOGLE_CLIENT_ID"),
clientSecret = System.getenv("GOOGLE_CLIENT_SECRET"),
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile")
)
}
client = HttpClient(Apache)
}
}
routing {
authenticate("auth-oauth-google") {
get("/login") {}
get("/callback") {
val principal: OAuthAccessTokenResponse.OAuth2? = call.authentication.principal()
call.sessions.set(UserSession(principal?.accessToken.toString()))
call.respondRedirect("/hello")
}
}
}
}
data class UserSession(val accessToken: String)
しかし全体のコードだけでは理解がしにくいので、3つの重要な箇所に分けて説明をします。
OAuthの実装
まずはOAuthをプラグインを用いて実装している部分になります。
install(Authentication)
はAuthenticationプラグインの読み込み、oauth("auth-oauth-google")
ではauth-oauth-google
という名前のOAuthプロバイダーを作成しています。
oauth
の中身のurlProvider
プロパティでは、承認が完了したあとにリダイレクトさせる遷移先のURLを、providerLookup
プロパティでは、必要なプロバイダーのOAuth設定を指定しています。そしてclient
プロパティでは、OAuthサーバーにリクエストを送信するために使用するHttpClient
を指定しています。
install(Authentication) {
oauth("auth-oauth-google") {
urlProvider = { "http://localhost:8080/callback" }
providerLookup = {
OAuthServerSettings.OAuth2ServerSettings(
name = "google",
authorizeUrl = "https://accounts.google.com/o/oauth2/auth",
accessTokenUrl = "https://accounts.google.com/o/oauth2/token",
requestMethod = HttpMethod.Post,
clientId = System.getenv("GOOGLE_CLIENT_ID"),
clientSecret = System.getenv("GOOGLE_CLIENT_SECRET"),
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile")
)
}
client = HttpClient(Apache)
}
}
セッションの実装
次にセッションの設定をしている部分を説明します。
この部分では、data classを使いUserSession
というセッションを定義し、セッションをCookieにuser_session
という名前で保存しています。
これによりcall.sessions.set()
やcall.sessions.get<UserSession>()
を使うことができるようになります。
fun Application.configureSecurity() {
install(Sessions) {
cookie<UserSession>("user_session")
}
... // 省略
}
data class UserSession(val accessToken: String)
ルーティングの実装
最後はrouting
で、この部分で実際のルーティングを行っています。
/login
にアクセスするとOAuthが行われ、認証に成功すると/callback
にリダイレクトされ続きの処理が行われます。
今回は/callback
で認証の情報を取り出し、セッションにaccessToken
を保存してから/hello
へリダイレクトしています。
(※この実装は確認のための仮実装のため、本番ではセッションに直接accessToken
を保存しないでください。)
またget("/login") {}
は若干奇妙ですが、実はauthenticate("auth-oauth-google")
が自動でurlProvider
にリダイレクトしてくれるため空でも動きます。
routing {
authenticate("auth-oauth-google") {
get("/login") {}
get("/callback") {
val principal: OAuthAccessTokenResponse.OAuth2? = call.authentication.principal()
call.sessions.set(UserSession(principal?.accessToken.toString()))
call.respondRedirect("/hello")
}
}
}
その他の実装と起動
Routing.kt
にはSecurity.kt
で実装したリダイレクト先を新しく追加しています。
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
+
+ get("/hello") {
+ call.respondText("Hello World!\n${call.sessions.get<UserSession>()?.accessToken}")
+ }
}
}
またApplication.kt
では、Security.kt
で作成したconfigureSecurity
を新しく読み込みます。
fun main(args: Array<String>): Unit =
io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused") // application.conf references the main function. This annotation prevents the IDE from marking it as unused.
fun Application.module() {
configureRouting()
+ configureSecurity()
}
あとは./gradlew run
で起動し、http://localhost:8080/login
にアクセスすることで、OAuthを利用できることが確認できます。
認証に成功すると、http://localhost:8080/hello
にリダイレクトされ、Hello World!と自分のaccessTokenを確認できるはずです。
さいごに
これで一通りのGoogleのOAuth 2.0のみの利用の確認ができました。
しかし、本来であればユーザー登録のための実装や複数OAuthのためのルーティング設計などもしなくてはいけません。
また、ritou様からご指摘をいただいた通り、OAuthを使ったログイン認証を実装する際にはOpenID Connectという仕組みを使う方が良さそうなので追記させていただきます。
そこで今後、もう少し実践的なアプリの実装を行うことでログイン周りの実装を更に詰めていきたいと思っています。
参考
ちなみに自分が作成した実装サンプルも一応参考までに置いておきます。
Author And Source
この問題について(KtorでOAuthプラグインを利用する方法), 我々は、より多くの情報をここで見つけました https://zenn.dev/yamachoo/articles/ktor-google-oauth著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol