Amplify Authenticationのトークンってどこに保存されているの?
Amplify LibrariesのAuthenticationはフロントエンド側のコーティングだけで簡単にユーザー認証機能を作成できます。
内部的にはAmazon Cognitoを用いていますが、アクセストークンをどのように保持しているのかが気になったので、実装を見てみました。
結論
デフォルトではlocalStorageに保存している。
書かないこと
Amplify Librariesの使い方
AWS Amplify
AmplifyはAWSの他サービスと連携してフルスタックのWeb・モバイルアププリケーションを作成するサービス群です。
現在はこれらのサービスで構成されています。
- AWS嬢へのバックエンド作成をサポートするAmplify CLI
- フロントエンドからの組み込みを行うためのAmplify Libraries
- Webアプリのデプロイやホスティングを行うAmplify Hosting
- GUIでアプリケーション開発ができるAmplify Studio
- React, Vue, Flutterなどに機能を埋め込めるAmplify UI Components
Amplify FrameworkとかAmplify Consoleって名称はいつなくなったんだ
Amplify Libraries
ライブラリはaws-amplifyというnpm packageで配布されています。
SignInのサンプルコードは
ドキュメントにこのように記載されています。
import { Auth } from "aws-amplify";
type SignInParameters = {
username: string;
password: string;
};
export async function signIn({ username, password }: SignInParameters) {
try {
const user = await Auth.signIn(username, password);
} catch (error) {
console.log("error signing in", error);
}
}
パスワードを用いた認証の場合はAuth.signInWithPassword
で実際の認証処理を行なっています。
private signInWithPassword(
authDetails: AuthenticationDetails
): Promise<CognitoUser | any> {
if (this.pendingSignIn) {
throw new Error('Pending sign-in attempt already in progress');
}
const user = this.createCognitoUser(authDetails.getUsername());
this.pendingSignIn = new Promise((resolve, reject) => {
user.authenticateUser(
authDetails,
this.authCallbacks(
user,
value => {
this.pendingSignIn = null;
resolve(value);
},
error => {
this.pendingSignIn = null;
reject(error);
}
)
);
});
return this.pendingSignIn;
}
ここで認証結果を元にCognitoUser
のインスタンスを生成しています。
export default class CognitoUser {
/**
* Constructs a new CognitoUser object
* @param {object} data Creation options
* @param {string} data.Username The user's username.
* @param {CognitoUserPool} data.Pool Pool containing the user.
* @param {object} data.Storage Optional storage object.
*/
constructor(data) {
if (data == null || data.Username == null || data.Pool == null) {
throw new Error("Username and Pool information are required.");
}
this.username = data.Username || "";
this.pool = data.Pool;
this.Session = null;
this.client = data.Pool.client;
this.signInUserSession = null;
this.authenticationFlowType = "USER_SRP_AUTH";
this.storage = data.Storage || new StorageHelper().getStorage();
this.keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
this.userDataKey = `${this.keyPrefix}.${this.username}.userData`;
}
// 中略
}
ここで、オプションとして指定されていない場合はthis.storage
にStorageHelper.getStorage()
を設定しています。
StorageHelper.getStorage
で取得されるのはstorageWindow
であり、constuctor
ではlocalStorageが設定されています。
export default class StorageHelper {
/**
* This is used to get a storage object
* @returns {object} the storage
*/
constructor() {
try {
this.storageWindow = window.localStorage;
this.storageWindow.setItem('aws.cognito.test-ls', 1);
this.storageWindow.removeItem('aws.cognito.test-ls');
} catch (exception) {
this.storageWindow = MemoryStorage;
}
/**
* This is used to return the storage
* @returns {object} the storage
*/
getStorage() {
return this.storageWindow;
}
}
CognitoUser.cacheToken()
でthis.storage
に各tokenを保存しています。
cacheTokens() {
const keyPrefix = `CognitoIdentityServiceProvider.${this.pool.getClientId()}`;
const idTokenKey = `${keyPrefix}.${this.username}.idToken`;
const accessTokenKey = `${keyPrefix}.${this.username}.accessToken`;
const refreshTokenKey = `${keyPrefix}.${this.username}.refreshToken`;
const clockDriftKey = `${keyPrefix}.${this.username}.clockDrift`;
const lastUserKey = `${keyPrefix}.LastAuthUser`;
this.storage.setItem(
idTokenKey,
this.signInUserSession.getIdToken().getJwtToken()
);
this.storage.setItem(
accessTokenKey,
this.signInUserSession.getAccessToken().getJwtToken()
);
this.storage.setItem(
refreshTokenKey,
this.signInUserSession.getRefreshToken().getToken()
);
this.storage.setItem(
clockDriftKey,
`${this.signInUserSession.getClockDrift()}`
);
this.storage.setItem(lastUserKey, this.username);
}
所感
amplify authでcognitoを初期設定で用いる時はどのtokenもlocalstorageに保管しています。(オプションとして他の場所を指定することは可能です。)
token認証は、ステートレスなのでスケールアウトしやすいというメリットを持ちますが、一度tokenを発行すると期限が切れるまで無効化しにくいという特徴を持ちます。
一般的にはaccessTokenの期限を短くし、refreshTokenの期限を相対的に長くすることで、漏洩時のリスクとユーザーの利便性を保つことがなされているかと思います。
しかし、クライアント側にrefreshTokenを保存する場合はこの戦略を用いることができないので、どういったプラクティスを用いられているのかが気になりました。
NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください! ※エンジニア以外も記事を投稿することがあります
Discussion