OAuth
必須依存関係: io.ktor:ktor-server-auth
コード例: auth-oauth-google
OAuthは、アクセス委任のためのオープンな標準規格です。OAuthは、Google、Facebook、Twitterなどの外部プロバイダーを使用して、アプリケーションのユーザーを承認するために使用できます。
oauthプロバイダーは認可コードフローをサポートしています。OAuthパラメーターを一箇所で設定でき、Ktorは必要なパラメーターとともに指定された認可サーバーへ自動的にリクエストを行います。
Ktorにおける認証と認可に関する一般的な情報は、Ktorサーバーでの認証と認可セクションで確認できます。
依存関係を追加する
OAuthを使用するには、ビルドスクリプトにktor-server-authアーティファクトを含める必要があります:
セッションプラグインをインストールする
クライアントが保護されたリソースにアクセスしようとするたびに認可を要求するのを避けるため、認可が成功した際にアクセストークンをセッションに保存することができます。 その後、保護されたルートのハンドラー内で現在のセッションからアクセストークンを取得し、それを使用してリソースを要求できます。
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") {
// Configure oauth authentication
urlProvider = { "http://localhost:8080/callback" }
}
}
}OAuthの設定
このセクションでは、Googleを使用してアプリケーションのユーザーを認可するためのoauthプロバイダーの設定方法を説明します。完全に実行可能な例については、auth-oauth-googleを参照してください。
前提条件: 認可クレデンシャルを作成する
Google APIにアクセスするには、Google Cloud Consoleで認可クレデンシャルを作成する必要があります。
Google Cloud Consoleで認証情報ページを開きます。
認証情報を作成をクリックし、
OAuth クライアント IDを選択します。ドロップダウンから
ウェブ アプリケーションを選択します。次の設定を指定します:
- 承認済みのJavaScript生成元:
http://localhost:8080。 - 承認済みのリダイレクトURI:
http://localhost:8080/callback。 Ktorでは、urlProviderプロパティを使用して、認可完了時に開かれるリダイレクトルートを指定します。
- 承認済みのJavaScript生成元:
作成をクリックします。
表示されたダイアログで、作成されたクライアントIDとクライアントシークレットをコピーします。これらは
oauthプロバイダーの設定に使用されます。
ステップ1: HTTPクライアントを作成する
oauthプロバイダーを設定する前に、サーバーがOAuthサーバーにリクエストを行うために使用するHttpClientを作成する必要があります。ContentNegotiationクライアントプラグインとJSONシリアライザーは、APIへのリクエスト後に受信したJSONデータをデシリアライズするために必要です。
val applicationHttpClient = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}クライアントインスタンスは、サーバーテストで別のクライアントインスタンスを作成できるように、mainモジュール関数に渡されます。
fun Application.main(httpClient: HttpClient = applicationHttpClient) {
}ステップ2: OAuthプロバイダーを設定する
以下のコードスニペットは、auth-oauth-googleという名前でoauthプロバイダーを作成および設定する方法を示しています。
val redirects = mutableMapOf<String, String>()
install(Authentication) {
oauth("auth-oauth-google") {
// Configure oauth authentication
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"),
extraAuthParameters = listOf("access_type" to "offline"),
onStateCreated = { call, state ->
//saves new state with redirect url value
call.request.queryParameters["redirectUrl"]?.let {
redirects[state] = it
}
}
)
}
client = httpClient
}
}urlProviderは、認可が完了したときに呼び出されるリダイレクトルートを指定します。このルートが承認済みのリダイレクトURIのリストに追加されていることを確認してください。
providerLookupを使用すると、必要なプロバイダーのOAuth設定を指定できます。これらの設定はOAuthServerSettingsクラスによって表現され、KtorがOAuthサーバーへ自動的にリクエストを行うことを可能にします。clientプロパティは、KtorがOAuthサーバーへリクエストを行うために使用するHttpClientを指定します。
ステップ3: ログインルートを追加する
oauthプロバイダーを設定した後、authenticate関数内にoauthプロバイダーの名前を受け入れる保護されたログインルートを作成する必要があります。Ktorがこのルートへのリクエストを受信すると、providerLookupで定義されたauthorizeUrlに自動的にリダイレクトされます。
routing {
authenticate("auth-oauth-google") {
get("/login") {
// Redirects to 'authorizeUrl' automatically
}
}
}ユーザーは、Ktorアプリケーションに必要な権限レベルを示す認可ページを目にします。これらの権限は、providerLookupで指定されたdefaultScopesに依存します。
ステップ4: リダイレクトルートを追加する
ログインルートとは別に、ステップ2: OAuthプロバイダーの設定で指定されているように、urlProviderのリダイレクトルートを作成する必要があります。
このルート内では、call.principal関数を使用してOAuthAccessTokenResponseオブジェクトを取得できます。OAuthAccessTokenResponseを使用すると、OAuthサーバーから返されたトークンやその他のパラメーターにアクセスできます。
routing {
authenticate("auth-oauth-google") {
get("/login") {
// Redirects to 'authorizeUrl' automatically
}
get("/callback") {
val currentPrincipal: OAuthAccessTokenResponse.OAuth2? = call.principal()
// redirects home if the url is not found before authorization
currentPrincipal?.let { principal ->
principal.state?.let { state ->
call.sessions.set(UserSession(state, principal.accessToken))
redirects[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を参照してください。
