OAuth
必要な依存関係: io.ktor:ktor-server-auth
コード例: auth-oauth-google
OAuth は、アクセス権限の委譲(access delegation)のためのオープン標準です。OAuthを使用すると、Google、Facebook、Twitterなどの外部プロバイダーを利用して、アプリケーションのユーザーを認可できます。
oauth プロバイダーは、認可コードフロー(authorization code flow)をサポートしています。OAuthのパラメータを1か所で設定でき、Ktorは必要なパラメータを使用して指定された認可サーバーに自動的にリクエストを送信します。
Ktorにおける認証と認可に関する一般的な情報は、Ktor Serverにおける認証と認可セクションを参照してください。
依存関係の追加
OAuthを使用するには、ビルドスクリプトに ktor-server-auth アーティファクトを含める必要があります。
Sessionsプラグインのインストール
クライアントが保護されたリソースにアクセスしようとするたびに認可を要求するのを避けるために、認可が成功した際にアクセストークンをセッションに保存できます。 その後、保護されたルートのハンドラー内で現在のセッションからアクセストークンを取得し、それを使用してリソースをリクエストできます。
import io.ktor.server.sessions.*
fun Application.main(httpClient: HttpClient = applicationHttpClient) {
install(Sessions) {
cookie<UserSession>("user_session")
}
}
@Serializable
data class UserSession(val state: String, val token: String)OAuth認可フロー
KtorアプリケーションにおけるOAuth認可フローは、以下のようになります。
ユーザーがKtorアプリケーションのログインページを開きます。
Ktorは特定のプロバイダーの認可ページへ自動的にリダイレクトし、必要なパラメータを渡します。
- 選択したプロバイダーのAPIにアクセスするためのクライアントID。
- 認可完了後に開かれるKtorアプリケーションのページを指定する、コールバックまたはリダイレクトURL。
- Ktorアプリケーションが必要とするサードパーティリソースのスコープ。
- アクセストークンを取得するために使用されるグラントタイプ(認可コード)。
- CSRF攻撃の緩和およびユーザーのリダイレクトに使用される
stateパラメータ。 - 特定のプロバイダー固有のオプションパラメータ。
認可ページには、Ktorアプリケーションが要求する権限レベルを示す同意画面が表示されます。これらの権限は、ステップ2: OAuthプロバイダーの設定で設定したスコープに依存します。
ユーザーが要求された権限を承認すると、認可サーバーは指定されたリダイレクトURLにリダイレクトし、認可コードを送信します。
Ktorは指定されたアクセストークンURLに対して、以下のパラメータを含む自動リクエストをもう一度送信します。
- 認可コード。
- クライアントIDとクライアントシークレット。
認可サーバーはアクセストークンを返して応答します。
クライアントはこのトークンを使用して、選択したプロバイダーの必要なサービスにリクエストを送信できます。ほとんどの場合、トークンは
Bearerスキームを使用してAuthorizationヘッダーで送信されます。サービスはトークンを検証し、そのスコープを認可に使用して、要求されたデータを返します。
OAuthのインストール
oauth 認証プロバイダーをインストールするには、install ブロック内で oauth 関数を呼び出します。オプションで、プロバイダー名を指定することもできます。 例えば、"auth-oauth-google" という名前で oauth プロバイダーをインストールする場合は、以下のようになります。
import io.ktor.server.application.*
import io.ktor.server.auth.*
fun Application.main(httpClient: HttpClient = applicationHttpClient) {
install(Authentication) {
oauth("auth-oauth-google") {
// oauth認証の設定
urlProvider = { "http://localhost:8080/callback" }
client = httpClient
}
}
}OAuthの設定
このセクションでは、Googleを使用してアプリケーションのユーザーを認可するための oauth プロバイダーの設定方法を説明します。 実行可能な完全な例については、auth-oauth-google を参照してください。
事前準備: 認可資格情報の作成
Google APIにアクセスするには、Google Cloud Consoleで認可資格情報を作成する必要があります。
Google Cloud Consoleの認証情報ページを開きます。
認証情報を作成をクリックし、
OAuth クライアント IDを選択します。ドロップダウンから
ウェブ アプリケーションを選択します。以下の設定を指定します。
- 承認済みの JavaScript 生成元:
http://localhost:8080 - 承認済みのリダイレクト URI:
http://localhost:8080/callbackKtorでは、認可完了時に開かれるリダイレクトルートを指定するために urlProvider プロパティが使用されます。
- 承認済みの JavaScript 生成元:
作成をクリックします。
表示されたダイアログで、
oauthプロバイダーの設定に使用する、作成されたクライアントIDとクライアントシークレットをコピーします。
ステップ1: HTTPクライアントの作成
oauth プロバイダーを設定する前に、サーバーがOAuthサーバーにリクエストを送信するために使用する HttpClient を作成する必要があります。APIへのリクエスト後に受信したJSONデータをデシリアライズするために、JSONシリアライザーを備えた ContentNegotiation クライアントプラグインが必要です。
val applicationHttpClient = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}クライアントインスタンスは、サーバーのテストで別のクライアントインスタンスを作成できるように、main モジュール関数に渡されます。
fun Application.main(httpClient: HttpClient = applicationHttpClient) {
}ステップ2: OAuthプロバイダーの設定
以下のコードスニペットは、auth-oauth-google という名前で oauth プロバイダーを作成および設定する方法を示しています。 固定のOAuth設定を持つプロバイダーの場合は、settings プロパティを使用します。
val redirects = ConcurrentMap<String, String>()
install(Authentication) {
oauth("auth-oauth-google") {
// oauth認証の設定
urlProvider = { "http://localhost:8080/callback" }
settings = 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").orEmpty(),
clientSecret = System.getenv("GOOGLE_CLIENT_SECRET").orEmpty(),
defaultScopes = listOf("https://www.googleapis.com/auth/userinfo.profile"),
extraAuthParameters = listOf("access_type" to "offline"),
onStateCreated = { call, state ->
// 新しいstateをリダイレクトURLの値とともに保存
call.request.queryParameters["redirectUrl"]?.let {
redirects[state] = it
}
}
)
fallback = { cause ->
if (cause is OAuth2RedirectError) {
respondRedirect("/login-after-fallback")
} else {
respond(HttpStatusCode.Forbidden, cause.message)
}
}
client = httpClient
}
}urlProviderは、認可完了時に呼び出されるリダイレクトルートを指定します。このルートが承認済みのリダイレクト URIのリストに追加されていることを確認してください。
settingsプロパティは、プロバイダーの静的なOAuth設定を指定します。これらの設定は OAuthServerSettings クラスによって表され、KtorがOAuthサーバーに対して自動リクエストを行えるようにします。静的なプロバイダー設定にはproviderLookupよりもsettingsを優先してください。これにより、Ktorが生成されたOpenAPI仕様のメタデータを推論することも可能になります。fallbackプロパティは、リダイレクトまたはカスタムレスポンスで応答することにより、OAuthフローのエラーを処理します。clientプロパティは、KtorがOAuthサーバーにリクエストを送信するために使用する HttpClient を指定します。
特定の呼び出しに対して動的にOAuth設定を解決するために、
providerLookupプロパティも引き続きサポートされています。プロバイダーの設定がリクエストデータ(テナント固有の資格情報やエンドポイントなど)に依存する場合に使用してください。
ステップ3: ログインルートの追加
oauth プロバイダーを設定した後に、oauth プロバイダーの名前を受け取る authenticate 関数内に保護されたログインルートを作成する必要があります。Ktorがこのルートへのリクエストを受信すると、settings で定義された authorizeUrl へ自動的にリダイレクトされます。
routing {
authenticate("auth-oauth-google") {
get("/login") {
// 自動的に 'authorizeUrl' にリダイレクトされます
}
}
}ユーザーには、Ktorアプリケーションが必要とする権限レベルを示す認可ページが表示されます。これらの権限は、settings で指定された defaultScopes に依存します。
ステップ4: リダイレクトルートの追加
ログインルートとは別に、ステップ2: OAuthプロバイダーの設定で指定した urlProvider 用のリダイレクトルートを作成する必要があります。
このルート内では、call.principal 関数を使用して OAuthAccessTokenResponse オブジェクトを取得できます。OAuthAccessTokenResponse を使用すると、トークンやOAuthサーバーから返されたその他のパラメータにアクセスできます。
routing {
authenticate("auth-oauth-google") {
get("/login") {
// 自動的に 'authorizeUrl' にリダイレクトされます
}
get("/callback") {
val currentPrincipal: OAuthAccessTokenResponse.OAuth2? = call.principal()
// 認可前にURLが見つからない場合はホームにリダイレクト
currentPrincipal?.let { principal ->
principal.state?.let { state ->
call.sessions.set(UserSession(state, principal.accessToken))
redirects.remove(state)?.let { redirect ->
call.respondRedirect(redirect)
return@get
}
}
}
call.respondRedirect("/home")
}
}
}この例では、トークン受信後に以下のアクションが実行されます。
- トークンがセッションに保存されます。この内容は他のルート内からアクセスできます。
- ユーザーは、Google APIへのリクエストが行われる次のルートへリダイレクトされます。
- 要求されたルートが見つからない場合、ユーザーは
/homeルートにリダイレクトされます。
ステップ5: APIへのリクエスト
リダイレクトルート内でトークンを受信してセッションに保存した後、このトークンを使用して外部APIにリクエストを送信できます。以下のコードスニペットは、HttpClient を使用してそのようなリクエストを行い、Authorization ヘッダーでこのトークンを送信してユーザー情報を取得する方法を示しています。
リクエストを行い、レスポンスボディを返す getPersonalGreeting という新しい関数を作成します。
private suspend fun getPersonalGreeting(
httpClient: HttpClient,
userSession: UserSession
): UserInfo = httpClient.get("https://www.googleapis.com/oauth2/v2/userinfo") {
headers {
append(HttpHeaders.Authorization, "Bearer ${userSession.token}")
}
}.body()その後、get ルート内でこの関数を呼び出して、ユーザー情報を取得できます。
get("/{path}") {
val userSession: UserSession? = getSession(call)
if (userSession != null) {
val userInfo: UserInfo = getPersonalGreeting(httpClient, userSession)
call.respondText("Hello, ${userInfo.name}!")
}
}実行可能な完全な例については、auth-oauth-google を参照してください。
