Amplify Gen2のLINEログイン時にIDトークンを保存する
前回、Amplify Gen2にLINEをOIDCプロバイダーとして登録し、ログインできるようにした。
続いて、LINE公式アカウントのユーザーと紐づけたいので、アクセストークンまたはIDトークンを取得できないか調べたところ、できたのでメモ。
環境
前回同様、 @aws-amplify/backend v1.0.4, @aws-amplify/backend-cli (ampx) v1.2.1。
フロントエンドの実装にはReact + Next.jsを利用。
調査
LINEのアクセストークンまたはIDトークンを取得できれば、APIを実行してユーザーIDを取得できる。
Cognitoの属性か、preSignUpトリガー内でいずれかのトークンが取得できないか調査したところ、以下の記述を発見。
You can map identity provider (IdP) tokens to custom attributes in your user pool. Social providers present an access token, and OIDC providers present an access and ID token. To map a token, add a custom attribute with a maximum length of 2,048 characters, grant your app client write access to the attribute, and map access_token or id_token from the IdP to the custom attribute.
実装
amplify/auth/resource.ts の設定
amplify/auth/resource.ts
の defineAuth
の引数に userAttributes
を追加し、カスタム属性を設定。 oidc
に attributeMapping
を追加し、 custom: { 'カスタム属性名': 'トークン名' }
を指定すると、トークン名が id_token
ならIDトークン、 access_token
ならアクセストークンが取得できる。
export const auth = defineAuth({ loginWith: { email: true, externalProviders: { google: { clientId: secret('GOOGLE_CLIENT_ID'), clientSecret: secret('GOOGLE_CLIENT_SECRET'), scopes: ['email'], }, oidc: [ { name: 'LINE', clientId: secret('LINE_CHANNEL_ID_'), clientSecret: secret('LINE_CHANNEL_SECRET'), issuerUrl: 'https://access.line.me', scopes: ['openid', 'email'], // 追加 attributeMapping: { custom: { 'custom:line_id_token': 'id_token' }, }, }, ], // 中略 }, }, // 追加、ユーザーID用のカスタム属性も設定しておく userAttributes: { 'custom:line_id_token': { dataType: 'String', maxLen: 2048 }, 'custom:line_user_id': { dataType: 'String', maxLen: 33 }, }, // 後略
この設定で、カスタム属性「custom:line_id_token」に、IDトークンが保存される。なお、アクセストークンではなくIDトークンを選んだのは、verifyで有効性を確認しつつIDトークンを取得できるため。アクセストークンの場合、verifyとユーザー情報取得が分かれている。*1
preSignUpトリガーの実装
preSignUpトリガーにて、IDトークンのverifyを実装する。
まず、IDトークンのverifyにはLINEチャネルIDが必要なため、シークレット LINE_CHANNEL_ID
として保存し、 amplify/auth/preSignUp/resource.ts
で環境変数として参照できるようにしておく。
export const preSignUp = defineFunction({ environment: { LINE_CHANNEL_ID: secret('LINE_CHANNEL_ID'), }, })
続いて、 amplify/auth/preSignUp/handler.ts
を実装。
export const handler: PreSignUpTriggerHandler = async event => { const lineIdToken = event.request.userAttributes['custom:line_id_token'] // LINE APIのサンプルコードでは、curlの-dオプションでbodyを送信している。 // Content-Typeはapplication/x-www-form-urlencodedになる。 const body = new URLSearchParams({ id_token: lineIdToken, client_id: process.env.LINE_CHANNEL_ID ?? '', }) const verifyResponse = await fetch('https://api.line.me/oauth2/v2.1/verify', { method: 'POST', body, }) // subがLINEユーザーID const { sub, error_description } = await verifyResponse.json() if (error_description) { throw new Error(error_description) } return event }
当初はsubを保存できないか試したが、カスタム属性に値が入らず。
同じような質問がStack Overflowにあった。preSignUpトリガー内では、属性の変更ができない?
解決できなかったため、preSignUpでは検証のみとし、postConfirmationトリガーでユーザーIDを保存することとする。
振り返り
これでIDトークンを保存し、またpreSignUpトリガー内でverifyできるようになった。ユーザーIDの保存は次の記事で。
トークンを取得できなければ、IDトークンではなくメールアドレスで突き合わせる必要があったので、だいぶ楽になった。
これでpreSignUpで直接属性を保存できればさらにありがたかった。なぜpreSignUp内で属性の登録ができないのか把握できていないが、他で使うこともなさそうなので、いったん打ち切り。
ちなみに、 userAttributes
にカスタム属性を指定できるようになったのは7月らしい。タイミングが良かった。