UserPoolを作成。デフォルト設定はこんな感じ。 必須項目や、確認メールの文面などを自由にカスタマイズでき、 登録時などのタイミングでLambdaを発火させることもできる。
作成したUserPoolにアプリクライアントを追加する。 ブラウザで使うのでクライアントシークレットはなし。
クライアント側
amazon-cognito-identity-jsを使う。
依存するjsを持ってくる。
$ wget https://raw.githubusercontent.com/aws/amazon-cognito-identity-js/master/dist/amazon-cognito-identity.min.js
$ wget https://raw.githubusercontent.com/aws/amazon-cognito-identity-js/master/dist/aws-cognito-sdk.min.js
Sign UpからAPIを呼ぶところまでのボタンを並べた。 SignInするとOIDC標準のトークンがそのページのドメインのLocal Storageに書かれる。
OpenID ConnectのIDトークンの内容と検証 - sambaiz-net
CognitoIdentityServiceProvider.<clientId>.<name>.idToken
CognitoIdentityServiceProvider.<clientId>.<name>.accessToken
CognitoIdentityServiceProvider.<clientId>.<name>.refreshToken
CognitoIdentityServiceProvider.<clientId>.<name>.clockDrift
CognitoIdentityServiceProvider.<clientId>.LastAuthUser
APIを呼ぶときはidTokenをAuthorization Headerに乗せる。
<button id="signUp">Sign Up</button>
<p><label>Code:<input type="text" id="code"></label></p>
<button id="confirm">Confirm</button>
<button id="signIn">Sign In</button>
<button id="whoAmI">Who am I?</button>
<button id="requestAPI">Request API with token</button>
<button id="signOut">Sign Out</button>
<script src="aws-cognito-sdk.min.js"></script>
<script src="amazon-cognito-identity.min.js"></script>
<script>
const USER_NAME = "*****";
const USER_PASSWORD = "*****";
const USER_EMAIL = "*****";
class CognitoUserPoolAuth {
constructor(UserPoolId, clientId, apiEndpoint) {
const poolData = {
UserPoolId : UserPoolId,
ClientId : clientId
};
this.userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
this.apiEndpoint = apiEndpoint
}
signUp(userName, password, email) {
const attributeList = [];
if (email) {
attributeList.push(new AmazonCognitoIdentity.CognitoUserAttribute({
Name : 'email',
Value : email
}));
}
return new Promise((resolve, reject) => {
this.userPool.signUp(userName, password, attributeList, null, (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
});
});
}
confirmCode(userName, confirmCode) {
const cognitoUser = this.getCognitoUser(userName);
return new Promise((resolve, reject) => {
cognitoUser.confirmRegistration(confirmCode, true, (err, result) => {
if (err) {
return reject(err);
}
resolve(result);
});
})
}
signIn(userName, password) {
const authenticationData = {
Username : userName,
Password : password,
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
const cognitoUser = this.getCognitoUser(userName);
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
resolve(result.getAccessToken().getJwtToken());
},
onFailure: function(err) {
reject(err);
},
});
});
}
signOut() {
const currentUser = this.currentUser();
if (!currentUser) return;
const cognitoUser = this.getCognitoUser(currentUser.username);
if (!cognitoUser) return;
cognitoUser.signOut();
}
getCognitoUser(userName) {
const userData = {
Username : userName,
Pool : this.userPool
};
return new AmazonCognitoIdentity.CognitoUser(userData);
}
currentUser() {
return this.userPool.getCurrentUser()
}
getJwtToken() {
return new Promise((resolve, reject) => {
const cognitoUser = this.currentUser();
if (!cognitoUser) {
return reject("unauthorized");
}
cognitoUser.getSession((err, result) => {
if (err) {
return reject(err);
}
resolve(result.getIdToken().getJwtToken());
});
})
}
async requestAPIWithToken() {
const token = await this.getJwtToken().catch(
(err) => {
console.log(err);
}
);
const headers = token ? { 'Authorization': token } : {};
return fetch(this.apiEndpoint, {
headers: headers
}).then((response) => {
return response.json();
});
}
}
// -------------
// Handler
// -------------
const auth = new CognitoUserPoolAuth(
"<poolID>",
"<clientID>",
"https://*****.execute-api.us-east-1.amazonaws.com/dev/secret"
)
document.getElementById("signUp").addEventListener("click", async () => {
const result = await auth.signUp(USER_NAME, USER_PASSWORD, USER_EMAIL).catch((err) => {
if (err.code === "UsernameExistsException") {
return Promise.reject("User name is already used");
} else {
return Promise.reject(err);
}
});
console.log(`signUp successfully`);
}, false);
document.getElementById("confirm").addEventListener("click", async () => {
const code = document.getElementById("code").value;
const result = await auth.confirmCode(USER_NAME, code);
console.log(`confirm successfully`);
}, false);
document.getElementById("signIn").addEventListener("click", async () => {
const result = await auth.signIn(USER_NAME, USER_PASSWORD).catch((err) => {
if (err.code === "UserNotConfirmedException") {
return Promise.reject("Confirm your email");
} else {
return Promise.reject(err);
}
});
console.log(`signIn successfully`);
}, false);
document.getElementById("whoAmI").addEventListener("click", async () => {
console.log(auth.currentUser());
}, false);
document.getElementById("requestAPI").addEventListener("click", async () => {
console.log(await auth.requestAPIWithToken());
}, false);
document.getElementById("signOut").addEventListener("click", async () => {
auth.signOut();
console.log("signout successfully");
}, false);
</script>
API側
Serverless FrameworkでCognitoのJWTを認証に使うには authorizerのarnにUserPoolのARNを入れる。
Serverless FrameworkでLambdaをデプロイする - sambaiz-net
$ cat serverless.yml
service: cognitoapi
provider:
name: aws
runtime: nodejs6.10
functions:
createTodo:
handler: handler.secret
events:
- http:
path: secret
cors: true
method: get
authorizer:
arn: ***** # UserPool's ARN
JWTのpayloadをdecodeして返してみる。
$ cat handler.js
'use strict';
module.exports.secret = (event, context, callback) => {
const payload = JSON.parse(
new Buffer(
event.headers.Authorization.split(".")[1],
"base64"
).toString()
);
const response = {
statusCode: 200,
body: JSON.stringify({
userInfo: payload
}),
headers: {
"Access-Control-Allow-Origin": "*"
},
};
callback(null, response);
};
こんな感じ。Sign Upしたときにコード付きのメールが送られている。