ReactとAmazon Cognitoで認証機能を作ってみる
Amazon Cognitoを使ったのでその覚書ついでに:pencil2: 細かい説明とかはないです:relaxed:
ユーザー管理機能を設定する前に
Amazon Cognitoでユーザーを管理できるようにしますので、とりあえず、Reactのアプリを作成します。
$ npx create-react-app react-aws-cognito --typescript
アプリが作成されたら、srcディレクトリの中に、Cognitoを利用するためのコンフィグファイルを作成します。
// awsConfiguration.ts const awsConfiguration = { region: 'ap-northeast-1', IdentityPoolId: 'ap-northeast-1:**********', UserPoolId: 'ap-northeast-1_**********', ClientId: '**********', } export default awsConfiguration
**********となっている部分はこれから説明します。
以下もインストールしてください。
$ yarn add amazon-cognito-identity-js
Cognitoをjsで楽にいじられるモジュールです。 https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js
ユーザープールを作成する
Cognitoに入ると上のような画面になるので、まずは「ユーザープールの管理」に入ります。 「ユーザープールを作成する」ボタンがあると思いますので、そこから作成画面へ入っていきます。
こんな具合で設定画面が出てくるので、順に設定します。
ユーザープールはそのままの意味で、ユーザーのみずたまりです。 ユーザーの倉庫のようなものです。
今回はデフォルトで作ります。
デフォルトの場合こんな設定になります。 必須の属性はemailとなり、これはサインアップ時に必須の項目となります(細かく設定するとこの辺りも変えられます)。
パスワードの最小長や多要素認証、また、登録時に送信するEメールやSMSの内容を編集したりもできます。 lambdaと連携させることも可能です:gear:
次にIDプールを作ります。
IDプールを作成する
フェデレーテッドアイデンティティから「新しいIDプールの作成」をクリックすると、以下のような画面が表示されます。
IDプール名を入力し、認証プロバイダーのところでCognitoタブを選択して必要な情報を入れます。
ユーザープールID
作成したユーザープールの「全般設定」(ユーザープールから作成したのを選択すると表示されます)にプールIDがあるのでこれを入力してください、
IDプール
作成したユーザープールの「アプリクライアント」からアプリクライアントを作成すると表示される、アプリクライアントIDを設定してください。
今回はJavascriptSDKを利用するのですが、その場合は「クライアントシークレットを生成」のチェックを外してください。JSのSDKでの対応ができていないようですね。。。 https://qiita.com/noobar/items/6615501b035e47792227
入力したら、「プールの作成」を押し、IAMロールを作成します。 ここのロールは、Authenticated UserとUnauthenticated Userそれぞれのアプリケーションの利用可能範囲を定めたものを表します。
Authenticated Userには、アプリケーションに登録したユーザーとして実行可能な範囲のアクションと、そのアクションに伴うAWSリソースへのアクセスを認めるべきです。 反対に、Unauthenticated UserにはAuthenticated Userと比較してより厳しい範囲でのアプリケーションの利用を認めるようにした方が無難です。
IDプールに紐づくIAMロールを作成するので、「許可」を押して次へ行きましょう。 作成が成功したら、「Amazon Cognitoでの作業開始」が出てくるので、ドロップダウンからjavascriptを選びます。 すると、js用のコードが出てきて、その中に、IdentityPoolIdが表記されているので、それをコピーしておきます。
ここまでで今一度、 IdentityPoolId、UserPoolId、 ClientIdを整理しておきましょう(冒頭のコンフィグファイルの項目です)。
IdentityPoolId ・・・ IDプール作った時に出てきたやつ。 UserPoolId ・・・ ユーザープールの「全般設定」に表示されるプールID。 ClientId ・・・ ユーザープールの「アプリクライアント」に表示されるアプリクライアントID。
上記は全てコピーして、冒頭で書いたawsConfiguration.tsに書き込みます。 srcディレクトリ内はこんな感じになります。:point_down_tone2:
. ├── auth(SignUp.tsx, Verification.tsx, SignIn.tsx, SignOut.tsx) ├── App.css ├── App.test.tsx ├── App.tsx ├── awsConfiguration.ts ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts └── serviceWorker.ts
サインアップ
tsxで書いたサインアップのコンポーネントです。 肝はSignUp関数です。
必須の属性として設定されていたemailとパスワードを入力すると、入力したemailあてにメールが届きます(もちろん電話番号で設定するとSMSでも届けられるように設定できます)。
このメールの文言もAWSコンソールで設定できます。 コードの有効期限はデフォルトでは7日になっているようです。
ちなみに、パスワードのポリシーはデフォルトで以下のようになっているので、注意してください。
// SignUp.tsx import React from 'react' import '../App.css' import { CognitoUserPool, CognitoUserAttribute } from "amazon-cognito-identity-js" import awsConfiguration from '../awsConfiguration' const userPool = new CognitoUserPool({ UserPoolId: awsConfiguration.UserPoolId, ClientId: awsConfiguration.ClientId, }) const SignUp: React.FC = () => { const [email, setEmail] = React.useState<string>('') const [password, setPassword] = React.useState<string>('') const changedEmailHandler = (event: any) => setEmail(event.target.value) const changedPasswordHandler = (event: any) => setPassword(event.target.value) const signUp = () => { const attributeList = [ new CognitoUserAttribute({ Name: 'email', Value: email }) ] userPool.signUp(email, password, attributeList, [], (err, result) => { if (err) { console.error(err) return } setEmail('') setPassword('') }) } return ( <div className="SignUp"> <h1 style={{ textAlign: 'left' }}>SignUp</h1> <input type="text" placeholder="email" onChange={changedEmailHandler} /> <input type="text" placeholder="password" onChange={changedPasswordHandler} /> <button onClick={signUp}>SignUp</button> </div> ) } export default SignUp
検証
サインアップで受け取った検証コードを使ってサインアップを完了しましょう。
検証コードと必須の属性であるemailを入力する以下のコードを実行すると、アカウントのステータスがUNCONFIRMEDだったのが画像のようにCONFIRMEDに変わります(ユーザープール/全般設定/ユーザーとグループ)。
// Verification.tsx import React from 'react' import '../App.css' import { CognitoUserPool, CognitoUser } from "amazon-cognito-identity-js" import awsConfiguration from '../awsConfiguration' const userPool = new CognitoUserPool({ UserPoolId: awsConfiguration.UserPoolId, ClientId: awsConfiguration.ClientId, }) const Verification: React.FC = () => { const [email, setEmail] = React.useState<string>('') const [verificationCode, setVerificationCode] = React.useState<string>('') const changedEmailHandler = (event: any) => setEmail(event.target.value) const changedVerificationCodeHandler = (event: any) => setAuthCode(event.target.value) const verifyCode = () => { const cognitoUser = new CognitoUser({ Username: email, Pool: userPool }) cognitoUser.confirmRegistration(authCode, true, (err: any) => { if (err) { console.log(err) return } console.log('verification succeeded') setEmail('') setAuthCode('') }) } return ( <div className="Verification"> <h1>Authenticate</h1> <input type="text" placeholder="verification code" onChange={changedVerificationCodeHandler} /> <input type="text" placeholder='email' onChange={changedEmailHandler} /> <button onClick={verifyCode}>Authenticate</button> </div> ) } export default Verification
サインイン
それでは検証成功したアカウントでサインインしてみます。 SignIn関数でサインインを行い、認証が成功すればonSuccessを通ってresultを返します。 ここではresultからaccessTokenを取り出してログに出しています。
// SignIn.tsx import React from 'react' import '../App.css' import { CognitoUserPool, CognitoUser, AuthenticationDetails } from "amazon-cognito-identity-js" import awsConfiguration from '../awsConfiguration' const userPool = new CognitoUserPool({ UserPoolId: awsConfiguration.UserPoolId, ClientId: awsConfiguration.ClientId, }) const SignIn: React.FC = () => { const [email, setEmail] = React.useState<string>('') const [password, setPassword] = React.useState<string>('') const changedEmailHaldler = (e: any) => setEmail(e.target.value) const changedPasswordHandler = (e: any) => setPassword(e.target.value) const signIn = () => { const authenticationDetails = new AuthenticationDetails({ Username : email, Password : password }) const cognitoUser = new CognitoUser({ Username: email, Pool: userPool }) cognitoUser.authenticateUser(authenticationDetails, { onSuccess: (result) => { console.log('result: ' + result) const accessToken = result.getAccessToken().getJwtToken() console.log('AccessToken: ' + accessToken) setEmail('') setPassword('') }, onFailure: (err) => { console.error(err) } }) } return ( <div className="SignIn"> <h1>SingIn</h1> <input type="text" placeholder='email' onChange={changedEmailHaldler}/> <input type="text" placeholder='password' onChange={changedPasswordHandler}/> <button onClick={signIn}>Sign In</button> </div> ) } export default SignIn
サインイン中のアカウントを検知する場合は、
const cognitoUser = userPool.getCurrentUser() if (cognitoUser) { // sign inしている状態 console.log('signing in') } else { // sign inしていない状態 console.log('no signing in') }
という感じで行い、サインアウト時もこれがtrueの時に処理できるようにします。
サインアウト
サインアウトはこちら。 userPool.getCurrentUser()で現在サインイン中のアカウントを検知し、 サインインされていればcognitoUser.signOut()を、 そうでなければとりあえず念の為localStorageを綺麗にしています(サインインするとlocalStorageにもaccessToken等のデータが入るので)。
// SignOut.tsx import React from 'react' import '../App.css' import { CognitoUserPool } from "amazon-cognito-identity-js" import awsConfiguration from '../awsConfiguration' const userPool = new CognitoUserPool({ UserPoolId: awsConfiguration.UserPoolId, ClientId: awsConfiguration.ClientId, }) const SignOut: React.FC = () => { const signOut = () => { const cognitoUser = userPool.getCurrentUser() if (cognitoUser) { cognitoUser.signOut() localStorage.clear() console.log('signed out') } else { localStorage.clear() console.log('no user signing in') } } return ( <div className="SignOut"> <h1>SignOut</h1> <button onClick={signOut}>Sign Out</button> </div> ) } export default SignOut
全部まとめて整理するとこんな感じ?:point_down_tone2:
// App.tsx import React from 'react' import './App.css' // components import SignUp from './auth/SignUp' import Verification from './auth/Verification' import SignIn from './auth/SignIn' import SignOut from './auth/SignOut' import { CognitoUserPool } from "amazon-cognito-identity-js" import awsConfiguration from './awsConfiguration' const userPool = new CognitoUserPool({ UserPoolId: awsConfiguration.UserPoolId, ClientId: awsConfiguration.ClientId, }) const App: React.FC = () => { const authentication = () => { const cognitoUser = userPool.getCurrentUser() // サインインユーザーがいればアプリのメイン画面へ、 // いなければサインアップ、検証、サインイン画面を表示する。 if (cognitoUser) { return ( <div className="authorizedMode"> <SignOut /> </div> ) } else { return ( <div className="unauthorizedMode"> <SignUp /> <Verification /> <SignIn /> </div> ) } } return ( <div className="App"> <header /> { authentication() } </div> ) } export default App
グループ(IAMロール)の設定
Cognitoでは特定のIAMロールを持つグループを作成することができ、 そのグループに属するユーザーはそのIAMロールのポリシーに従ってAWSリソースへのアクセスが制限されます。
IAMロールから割り当てたいロールを選択してグループを作成します。 以降、AWSコンソールからグループにユーザーを割り当てることができるようになります。
ですが、これ、毎回手動でAWSコンソールをいじるのも面倒すぎるので、JavascripSDKを利用してやりたいです。 adminAddUserToGroupを利用して実現します。 https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityServiceProvider.html#adminAddUserToGroup-property
import AWS from 'aws-sdk' const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider() const params = { GroupName: 'STRING_VALUE', /* required */ UserPoolId: 'STRING_VALUE', /* required */ Username: 'STRING_VALUE' /* required */ } cognitoidentityserviceprovider.adminAddUserToGroup(params, function(err, data) { if (err) console.log(err, err.stack); // an error occurred else console.log(data); // successful response })
上のように書くみたいです(試してませんが)。 検証が成功した後にこの処理を挟めば良いと思います。
Usernameはおそらくメールアドレスを入力すれば問題ないかと思います。 もしかしたら必須の属性設定によるかもしれませんが。。。
基本的には以上でCognitoを利用した認証機能を実装できるはずです:hugging:
デモ作ってみました。ここで説明したawsConfiguration.tsは入っていません。 https://github.com/yutaro1204/AmazonCognitoWithReact