前置き
通常、ソーシャルログインを実装するのに、そのソーシャルログインのSDKを導入することが一般的です。しかし、この方法ですと、SDKに依存してしまうため、対象プラットフォームのSDKがなければ、導入ができなくなります。たとえば、
- HMSのAndroid端末において、Google IDまたはApple IDでログインする場合
- HMS Coreが入っていないAndroid端末において、HUAWEI IDでログインする場合
- GMSのAndroid端末において、Apple IDでログインする場合
上記の状況ですと、SDK導入によるソーシャルログインの実装では対応が不可能です。
また、SDKはプラットフォームごとに異なるため、ソースコードの管理において手間が増えます。
そこで、解決策としてOauthが出てきます。Oauthを利用すれば、プラットフォームとソーシャルログインSDKに依存せずに、ソーシャルログインを実装できます。
Android端末限定であれば、AppAuth for AndroidというOauthに特化したAndroid専用のSDKあり、それを使えば、OAuth 2.0に対応したソーシャルログインを簡単に実装できます。
AppAuth for Androidの導入方法
対応OS
- Android 6.0以上
ライブラリの導入
appモジュールのbuild.gradleに
- AppAuth for Android(net.openid:appauth)
-
JWT Decoding library for Android(com.auth0.android:jwtdecode)
を追加します。
AppAuth for Androidを使ってソーシャルログインをし、JWT Decoding library for Androidを使ってログイン後に返ってくるIDトークンを解凍し、ユーザー情報を取得します。
// AppAuth SDK
implementation 'net.openid:appauth:{バージョン}'
// For decode ID Token
implementation 'com.auth0.android:jwtdecode:{バージョン}'
また、それぞれの最新バージョンはこちらでご確認ください。
AppAuth for Androidの最新バージョン
JWT Decoding library for Androidの最新バージョン
AndroidManifest.xmlの編集
AndroidManifest.xmlに次のようにリダイレクト用のactivityを追加します。
...
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="{your scheme}"
android:host="{your host}"
android:path="{your path}"/>
</intent-filter>
</activity>
...
<data>要素の中身は対象のソーシャルログインによって異なります。たとえば、
Google ID:
<data android:scheme="{アプリのパッケージ名}"/>
HUAWEI ID:
<data android:scheme="com.huawei.apps.{OAuth 2.0 client ID}"/>
Apple ID:
<data android:scheme="https"
android:host="{Developerサイトへ事前に登録したReturnURLsのホスト部分}"
android:path="{Developerサイトへ事前に登録したReturnURLsのパス部分}"/>
LINE ID:
<data android:scheme="https"
android:host="{LINE Developersコンソールに登録したコールバックURLのホスト部分}"
android:path="{LINE Developersコンソールに登録したコールバックURLのパス部分}"/>
Facebook ID:
<data android:scheme="https"
android:host="{クライアントOAuth設定に登録した有効なOAuthリダイレクトURIのホスト部分}"
android:path="{クライアントOAuth設定に登録した有効なOAuthリダイレクトURIのパス部分}"/>
AuthStateとAuthorizationServiceConfigurationを用意
空のAuthStateを生成し、ソーシャルログインの認証エンドポイントとトークンエンドポイントを用いてAuthorizationServiceConfigurationを生成します。
private val appAuthState: AuthState = AuthState.jsonDeserialize("{}")
private val config = AuthorizationServiceConfiguration(
Uri.parse({認証エンドポイント}),
Uri.parse({トークンエンドポイント})
)
サインイン
サインイン画面を表示
- ソーシャルログインのクライアントIDとリダイレクトURIと、前のステップで生成したAuthorizationServiceConfigurationを用いて、AuthorizationRequestを生成します。
- スコープを設定します。
- AuthorizationServiceのgetAuthorizationRequestIntentで、サインインのIntentを作ります。
- startActivityForResultでサインインのIntentを起動します。サインインの結果はonActivityResultで返ってくるので、startActivityではなく、startActivityForResultでIntentを起動しなければなりません。
fun signIn() {
val authorizationRequest = AuthorizationRequest
.Builder(
config,
{ソーシャルログインのクライアントID},
ResponseTypeValues.CODE,
Uri.parse({ソーシャルログインのクライアントのリダイレクトURI})
)
.setScope({スコープ。例:openid email profile})
.build()
val intent = AuthorizationService(context).getAuthorizationRequestIntent(authorizationRequest)
startActivityForResult(intent, requestCode)
}
サインインの認証結果を取得
- onActivityResultでサインインの認証結果を受け取ります。
- AuthStateを更新します。
認証成功の条件
AuthorizationResponseが空ではなく、かつAuthorizationExceptionが空である
認証失敗の条件
AuthorizationResponseが空、またはAuthorizationExceptionが空ではない
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val authorizationResponse = AuthorizationResponse.fromIntent(data)
val exception = AuthorizationException.fromIntent(data)
appAuthState.update(authorizationResponse, exception)
if (exception != null || authorizationResponse == null) {
// 認証失敗
} else {
// 認証成功
}
}
各トークンを取得
- AuthorizationResponseを使ってトークンを要求します。
- (1)のレスポンスを使ってAuthStateを更新します。
- AuthStateのaccessTokenにアクセストークンが入っています。
- AuthStateのrefreshTokenにリフレッシュトークンが入っています。
- AuthStateのidTokenにIDトークンが入っています。
AuthorizationService(context)
.performTokenRequest(authorizationResponse.createTokenExchangeRequest()) { tokenResponse, authorizationException ->
appAuthState.update(tokenResponse, authorizationException)
tokenResponse?.let {
// アクセストークン=appAuthState.accessToken
// リフレッシュトークン=appAuthState.refreshToken
// IDトークン=appAuthState.idToken
}
}
IDトークンをユーザー情報に変換
- JWTでIDトークンを解凍します。
- OpenID=jwt.subject
- メールアドレス=jwt.claims["email"]?.asString()
val jwt = JWT(idToken)
val openId = jwt.subject
val email = jwt.claims["email"]?.asString()