OpenID Connectを用いたSSOのフローと実装

0. OAuth 2.0とOpenID Connect

用語

  • OAuth Provider : 認可を提供する側(ex. Twitter, Google)
  • ソーシャルログインのことを SSO(Single Sign On) とも言う
  • OAuthに認証の機能を足したものがOpenID Connect
  • OAuth.io, Auth0, Firebase Authentication などの OAuth Providerなどをサービスとして提供しているものを IDaaS という
  • アクセストークンやユーザー情報をエンコードしたものを ID Token という

IDトークンが分かれば OpenID Connect が分かる - Qiita

OAuth は認証のためのプロトコルではない

→ Open ID Connectを使うべきだがTwitterとかは対応していないので仕方なくOAuthを使っている

OAuth 認証を真面目に考える | DevelopersIO

APIクライアントでOAuth認可でソーシャルログインを行い、返ってきたアクセストークン, ユーザー情報をAPIサーバーに送り, 検証してもらうことで認証を行うのは自然らしい

OAuth 2.0 全フローの図解と動画 - Qiita

一番分かりやすい OpenID Connect の説明 - Qiita

1. OpenID ConnectによるSSOのフロー

シーケンス図の構文と機能

@startuml
autonumber
 
participant Client
participant "API Server"
participant OAuth.io
participant "OAuth provider"
 
group OAuthIO.popup({providerId})
Client -> "OAuth provider": social login request
"OAuth provider" -> Client: show login form
Client -> "OAuth provider": login
"OAuth provider" -> OAuth.io: authentication code
OAuth.io -> "OAuth provider": request access token
"OAuth provider" -> OAuth.io: access token & idToken
Client <- OAuth.io: response IdToken
end
group API /login/{providerId}
Client -> "API Server": send IdToken to /login/{providerId}
"API Server" -> "API Server": verify IdToken
note over "API Server"
create or fetch user info
end note
"API Server" -> Client: user info & api access token as JWT
end
@enduml

![https://kroki.io/plantuml/png/eNqFkk1PwzAMhu_5FdYOCA7b7hNCmziVy5CYxDmk7mqtjUM-hhDiv-N-TS2r4JKD_byvnTfZhqh9THWldIpsU_2GXiknNTLktI3wWBHaOCktds8ZvKA_o19MGvtdiuWKeEq3VXCez5Q3CnX0nFwHZ_uVY5fc7dfQz_LvO9UNheXDlXoDgQ3pCio-kgWP7wlDVL-xRtqZiKDkjx4v2Nd_mrfYrNtwtw1IUqUYkNGR2ILhHNXQnXfttwRtDIYAkU_475ARCjdA-aEV9bvfL0eox-DYBoSsh9DmfcbNQ63bO63HAY8jGL2lRCXSwUZmz2rHimsHOan4vKxiOSKwFKefxnjUTcNDgdGUkIIQZAtudodGdDVmeM0LKqloR9OgdICn10MbwFYO-dc_ynr7zQ]()

Open ID Connectによる認証がクライアント側で完了するとクライアント側にIdTokenが返ってくるのでそれをAPIサーバーに送って検証することで認証とする

  1. ユーザーがサインイン, サインアップボタンを押す
  2. OAuth.ioがOAuth Providerにリクエストを送る
  3. callback URLで指定したURL(=OAuth.io)に認証情報が返る
  4. OAuth.ioはクライアントにIdToken含むユーザー情報を返す
  5. APIサーバーにIdTokenをPOSTする
  6. APIサーバーはIdTokenの検証をする
  7. IdTokenが妥当ならユーザー情報が取得できる(正確にはIdTokenをデコードしてユーザー情報を取り出している)
  8. ユーザー情報とJWTを返却する

2. OAuth.io

node.jsとかで利用できるOAuth Consumerを代行してくれるサービス

GitHub - oauth-io/oauth-js: OAuth that just works ! This is the JavaScript SDK for OAuth.io

気になる点

  • 開発がアクティブでない
  • JS製で型定義が無い
  • APIがコールバック関数の形

OAuth.popup() を使うには

oauthio-web

を使用する.

OAuth.popup() するとコールバック関数でcredential OAuthIOCredential (IdTokenなど)が得られる. 型定義がないのでオブジェクトを見て型をあてる.

2.1 OAuth.ioセットアップ

  1. oauth.io でDashBoardの API IntegrationAdd Integration から好きなProviderを選ぶ.

  2. client_idclient_secret をOAuth Provider(GCP上で確認できる)で示されたものに設定し Try Auth するとsocial loginを試すことができる.

  3. 成功するとどんな情報が返ってくるか, クライアントのコード例が確認できる

  4. クライアントに実装する

最小サンプル, oAuthIOPublicKey はOAuth.ioのダッシュボードから確認できる

import { OAuth } from 'oauthio-web'
 
const oAuthIOPublicKey = process.env.VUE_APP_OAUTHIO_PUBLIC_KEY;
OAuth.initialize(oAuthIOPublicKey);
OAuth.popup('google').done(async (cred) => {
	// cred.id_token が IdToken
  // APIサーバーにIdTokenをPOST
	const res = await fetch('/api/users/register/google', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ idToken: cred.id_token })
  })
})

3. Google

3.1 認証情報を作成する

GCPでOAuthクライアントの認証情報を作成する

GoogleAPI、OAuth2.0の有効化の手順 - サーバーワークスエンジニアブログ

3.2 GoogleのIdTokenの内容

sub, email, picture をclaimとしてもらえる

sub: は一意, 文字列長22

OpenID Connect | Google Identity | Google Developers

3.3 APIサーバーでのIdTokenの検証

Java製のGoogle API Clientがある

API Client Library for Java | Google Developers

Authenticate with a backend server | Google Sign-In for Websites

repositories {
	google()
}
 
dependencies {
	implementation('com.google.api-client:google-api-client:1.32.2')
}

API Clientを用いてIdTokenの検証を行う例

fun verifyIdToken(idToken: String): GoogleUserProfile {
    val transport = NetHttpTransport()
    val jsonFactory = GsonFactory()
    val verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
    // OAuth Consumer の clientId
        .setAudience(listOf(clientId))
        .build()
 
    val decodedIdToken = verifier.verify(idToken)
    // IdTokenからGoogleユーザー情報を作成
    return GoogleUserProfile.of(decodedIdToken.payload)
}
fun createUserWithGoogle(idToken: String) {
	// トークンの検証
  val profile = kotlin.runCatching {
    googleTokenVerifier.verifyIdToken(requestDto.idToken)
  }
    .onFailure { throw InvalidPasswordException() }
    .getOrThrow()
 
  // profile に sub, email, picture が入っているのでそれらを元に User を作成
  val user = User.from(profile)
}

GCP: Google OAuth2 認証情報

Google Cloud Platform

4. Twitter

todo :Twitter Developer Programを申請しアプリとして作成しなければならない

5. OAuth.ioを使わず自前でOIDC Providerを用意する方法