Amazon Cognito 구성 요소
- 사용자 풀(User Pool): 사용자 풀은 사용자를 관리하는 Cognito 서비스. 각 사용자는 이 풀 안에서 등록되고 인증된다.
- 사용자 속성: 사용자 풀에 등록된 사용자 정보에는 이메일, 비밀번호 등 다양한 속성이 있다.
- 클라이언트 앱(Client ID): 사용자 풀에 연결된 특정 앱(프론트엔드 또는 백엔드 서비스)이 사용자 인증을 요청할 수 있게 하는 ID다.
- 인증 토큰: 인증이 성공하면, Amazon Cognito는 인증된 사용자의 세션을 유지하기 위해 ID 토큰, 액세스 토큰, 갱신 토큰을 발급한다.
토큰의 종류
ID 토큰
- 용도: ID 토큰은 사용자의 인증 정보를 담고 있으며, 주로 사용자의 신원 확인을 위해 사용된다.
- 사용처: ID 토큰은 주로 애플리케이션 클라이언트에서 사용자가 로그인되어 있는지 확인하거나, 서버가 사용자에 대한 정보를 필요로 할 때 사용된다. 예를 들어, 사용자 이름이나 이메일을 포함한 정보를 애플리케이션에서 필요로 할 때 사용한다.
- 사용 예: 서버가 사용자 프로필 정보를 표시하거나 사용자 인증이 필요한 API 요청에서 사용된다.
액세스 토큰
- 용도: 액세스 토큰은 사용자가 특정 리소스(API)에 접근할 수 있는 권한을 인증한다.
- 사용처: 액세스 토큰은 AWS API Gateway나 다른 백엔드 서비스로 요청을 보낼 때 사용된다. 토큰이 유효하다면 서버는 요청을 처리하고, 유효하지 않다면 권한이 없다는 응답을 보낸다.
- 사용 예: 사용자가 특정 데이터를 요청하거나 데이터베이스에서 정보를 조회할 때, 이 토큰을 사용해 API 요청을 인증한다.
갱신 토큰
- 용도: 갱신 토큰은 액세스 토큰이나 ID 토큰이 만료된 경우, 새로운 토큰을 발급받기 위해 사용된다.
- 사용처: 사용자가 로그인한 상태를 유지하고 싶을 때, 만료된 토큰을 갱신할 수 있다. 갱신 토큰은 더 긴 유효 기간을 가지며, 주로 백그라운드에서 액세스 토큰을 갱신하는 데 사용된다.
- 사용 예: 사용자가 로그인한 상태로 웹사이트를 새로고침하거나, 세션이 일정 기간 이후 만료되었을 때, 다시 로그인하지 않고도 새로운 액세스 토큰을 받을 수 있다.
로그인 흐름
- 사용자 로그인 요청:
- 사용자가 애플리케이션에서 로그인 화면에 자신의 사용자 이름과 비밀번호를 입력하고 "로그인" 버튼을 클릭한다.
- 인증 요청 전송:
- 애플리케이션은 입력된 사용자 이름과 비밀번호를 Amazon Cognito 사용자 풀에 전송한다.
- 인증 처리:
- Cognito는 사용자 이름과 비밀번호를 확인한다. 이 정보가 올바른 경우, 사용자는 인증된다.
- JWT 발급:
- 인증이 성공하면, Cognito는 사용자의 세션을 식별하기 위해 JSON Web Token (JWT)을 생성한다. 이에는 Access Token, ID Token, Refresh Token이 포함된다.
- Access Token: API 호출 시 사용자의 인증을 확인하는 데 사용된다.
- ID Token: 사용자의 정보를 포함하며, 클라이언트 애플리케이션에서 사용된다.
- Refresh Token: Access Token이 만료된 경우 새로운 Access Token을 발급받기 위해 사용된다.
- 응답 수신:
- 애플리케이션은 Cognito로부터 JWT를 수신한다.
- 세션 관리:
- 애플리케이션은 JWT를 저장하고, 이후 API 호출 시 이 토큰을 사용하여 인증된 요청을 보낸다.
- 사용자 정보 확인 (선택적):
- 애플리케이션이 사용자 정보를 필요로 하는 경우, ID Token을 사용하여 사용자 정보를 확인할 수 있다.
- 로그아웃:
- 사용자가 로그아웃을 요청하면, 애플리케이션은 JWT를 삭제하고, Cognito 세션을 종료한다.
Amazon Cognito 사용자풀 생성
Amazon Cognito 콘솔창으로 들어가 '사용자 풀 생성' 버튼을 누른다.
Cognito 사용자 풀 로그인 옵션 탭에서 '사용자 이름'을 체크한다.
암호 정책을 설정한다. Cognito 기본값으로 해도 되고, 여러분들이 원하는 요구 사항을 지정해도 괜찮다. 필자는 '사용자 지정' 으로 정책을 설정해줬다.
멀티 팩터 인증은 '선택적 MFA' 를 선택해주고, MFA 방법에서 '인증 앱'과 'SMS 메시지'를 선택한다.
사용자 계정 복구는 아래와 같이 설정하고 '다음' 버튼을 누른다.
셀프 서비스 가입, 속성 확인 및 사용자 계정 확인 설정은 아래와 같이 기본설정은 유지한다. '다음' 버튼을 누른다.
SMS 설정은 IAM 역할을 새로 생성하고, 역할의 이름을 적는다. 이렇게 하면 Amazon Cognito에 SMS 메시지를 보낼 수 있는 권한을 부여하는 역할이 생성된다.
사용자 풀 이름을 적는다.
초기 앱 클라이언트 설정을 아래와 같이 해주고
앱 클라이언트 이름을 적는다.
고급 앱 클라이언트 설정탭의 '인증 흐름'에서 'ALLOW_USER_PASSWORD_AUTH' 를 추가한다.
'다음' 버튼을 누른다.
생성한 사용자 풀의 개요부분을 보면 '사용자 풀 ID'가 있다. 이를 기록해놓자.
또한, '앱 통합' 탭의 '앱 클라이언트 목록'에 들어가보면, '클라이언트 ID'를 확인할 수 있다. 이것도 기록해놓자.
기록해놓은 사용자 풀 ID와 클라이언트 ID는 향후 프론트엔드 코드에서 사용될 것이다.
AWS SDK for JavaScript
JavaScript에서 Cognito를 불러와 회원 가입, 로그인 기능을 구현하기 위해서는 아래의 파일이 필요하다.
- aws-cognito-sdk.min.js
- amazon-cognito-identity.min.js
- aws-sdk.js
- aws-sdk.min.js
아래 링크를 통해 aws-cognito-sdk와 amazon-cognito-identity를 다운로드하자.
- https://raw.githubusercontent.com/aws/amazon-cognito-identity-js/master/dist/aws-cognito-sdk.min.js
- https://raw.githubusercontent.com/aws/amazon-cognito-identity-js/master/dist/amazon-cognito-identity.min.js
- https://github.com/aws/aws-sdk-js/releases/download/v2.6.6/browser.zip
다운로드를 진행했다면, html과 css, javascript파일을 저장할 폴더에 보관하자.
소스코드
아래와 같이 코드를 작성하고, 하나의 디렉토리에 저장하도록 하자.
<!--login.html-->
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<script src="aws-sdk.min.js"></script>
<script src="aws-cognito-sdk.min.js"></script>
<script src="amazon-cognito-identity.min.js"></script>
<script src="main.js"></script>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<p class="fsize">Login</p>
<input type="text" id="email" placeholder="email">
<input type="password" id="password" placeholder="password">
<input class="fsize_Login_button" type="button" value="Login" onclick="Login();">
<input type="button" value="Sign Up" onclick="window.location.href='signup.html';">
</div>
</body>
</html>
<!--signup.html-->
<!DOCTYPE html>
<html>
<head>
<title>Sign Up</title>
<script src="aws-cognito-sdk.min.js"></script>
<script src="amazon-cognito-identity.min.js"></script>
<script src="main.js"></script>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<p class="fsize">Sign Up</p>
<input type="text" id="email" placeholder="email">
<input type="password" id="password" placeholder="password">
<input class="fsize_Sign_button" type="button" value="Sign Up" onclick="SignUp();">
<input type="button" value="Back" onclick="window.location.href='login.html';">
</div>
</body>
</html>
<!--confirm.html-->
<!DOCTYPE html>
<html>
<head>
<title>Confirm</title>
<script src="aws-cognito-sdk.min.js"></script>
<script src="amazon-cognito-identity.min.js"></script>
<script src="main.js"></script>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<p class="fsize">Confirm</p>
<input type="text" id="email" placeholder="email">
<input type="text" id="ConfirmCode" placeholder="verification code">
<input type="button" value="Confirm" onclick="ConfirmRegistration();">
</div>
</body>
</html>
// main.js
const poolData = {
UserPoolId: 'ap-northeast-2_**********', // 사용자 풀 ID
ClientId: '*******************', // 클라이언트 ID
};
function main() {
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
const cognitoUser = userPool.getCurrentUser();
const currentUserData = {};
if (cognitoUser != null) {
cognitoUser.getSession((err, session) => {
if (err) {
console.log(err);
location.href = "login.html";
} else {
cognitoUser.getUserAttributes((err, result) => {
if (err) {
location.href = "login.html";
}
for (let i = 0; i < result.length; i++) {
currentUserData[result[i].getName()] = result[i].getValue();
}
document.getElementById("email").value = currentUserData["email"];
const signoutButton = document.getElementById("signout");
signoutButton.addEventListener("click", event => {
cognitoUser.signOut();
localStorage.removeItem('isLoggedIn'); // 로그아웃 시 localStorage에서 제거
localStorage.removeItem('username');
location.reload();
});
signoutButton.hidden = false;
});
}
});
} else {
location.href = "login.html";
}
}
// signup.html
function SignUp() {
var username = document.getElementById("email").value;
var password = document.getElementById("password").value;
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
// 이메일 속성을 추가합니다.
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: username
};
var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
attributeList.push(attributeEmail);
userPool.signUp(username, password, attributeList, null, function(err) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
window.location.href = 'confirm.html';
});
}
// confirm.html
function ConfirmRegistration() {
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var username = document.getElementById("email").value;
var code = document.getElementById("ConfirmCode").value;
var userData = {
Username: username,
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.confirmRegistration(code, true, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
console.log('call result: ' + result);
window.location.href = 'login.html';
});
}
// login.html
function Login() {
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var username = document.getElementById("email").value;
var password = document.getElementById("password").value;
var authenticationData = {
Username: username,
Password: password,
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData
);
var userData = {
Username: username,
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var idToken = result.getIdToken().getJwtToken(); // ID 토큰
var accessToken = result.getAccessToken().getJwtToken(); // 액세스 토큰
var refreshToken = result.getRefreshToken().getToken(); // 갱신 토큰
console.log("idToken : " + idToken);
console.log("accessToken : " + accessToken);
console.log("refreshToken : " + refreshToken);
localStorage.setItem('isLoggedIn', 'true'); // sessionStorage에서 localStorage로 변경
localStorage.setItem('username', username);
window.location.href = '../main_page/index.html'; // 수정
},
onFailure: function(err) {
// 로그인에 실패 했을 경우 에러 메시지 표시
console.log(err);
alert("로그인 실패")
}
});
}
# main.css
.fsize{
font-size: 28px;
text-align: center;
}
.fsize_Login_button {
margin-bottom: 10px;
}
.fsize_Sign_button {
margin-bottom: 10px;
}
.container {
width: 280px;
margin: 0 auto;
padding: 50px;
border: 1px solid #ccc;
border-radius: 5px;
}
input[type="text"],
input[type="password"] {
width: 93%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
input[type="button"] {
width: 100%;
padding: 10px;
background-color: #0000FF;
color: #ffffff;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type="button"]:hover {
background-color: #0000FF;
}
button{
width: 100%;
padding: 10px;
background-color: #0000FF;
color: #ffffff;
border: none;
border-radius: 4px;
cursor: pointer;
}
회원가입 과정 흐름
SignUp() 함수는 사용자의 이메일과 비밀번호를 받아서 AWS Cognito의 사용자 풀(User Pool)에 새로운 사용자를 등록하는 역할을 한다.
function SignUp() {
var username = document.getElementById("email").value; // 이메일 입력값
var password = document.getElementById("password").value; // 비밀번호 입력값
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); // 사용자 풀 객체 생성
// 이메일 속성을 추가
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: username
};
var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
attributeList.push(attributeEmail); // 이메일 속성을 속성 리스트에 추가
userPool.signUp(username, password, attributeList, null, function(err) {
if (err) {
alert(err.message || JSON.stringify(err)); // 오류 발생 시 메시지 출력
return;
}
window.location.href = 'confirm.html'; // 회원가입 성공 시 확인 코드 입력 페이지로 이동
});
}
이 부분에서 사용자가 입력한 이메일과 비밀번호를 변수에 저장한다.
var username = document.getElementById("email").value;
var password = document.getElementById("password").value;
poolData에는 사용자 풀의 UserPoolId와 ClientId가 저장되어 있다. 이 데이터를 기반으로 Cognito 사용자 풀 객체를 생성한다. 이 객체는 AWS Cognito와 상호작용하여 사용자 등록 등의 작업을 처리한다.
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
회원가입 시 사용자는 특정 속성을 가진다. 여기서는 이메일을 속성으로 추가하고, CognitoUserAttribute 객체로 감싼 후 attributeList 배열에 추가한다. AWS Cognito는 사용자의 속성을 기반으로 신원을 관리한다.
var attributeList = [];
var dataEmail = {
Name: 'email',
Value: username
};
var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
attributeList.push(attributeEmail);
signUp() 메서드는 사용자 풀에 새로운 사용자를 등록하는 역할을 한다. 파라미터로는 사용자의 이메일(또는 유저 이름), 비밀번호, 속성 목록이 전달된다. 이 파라미터들을 AWS Cognito에 전송하여 새로운 계정을 생성한다.
회원가입이 성공하면 사용자는 confirm.html 페이지로 이동하여 이메일 확인 코드를 입력하게 된다.
회원가입 도중 오류가 발생하면, err.message를 통해 오류 메시지를 사용자에게 알린다.
userPool.signUp(username, password, attributeList, null, function(err) {
if (err) {
alert(err.message || JSON.stringify(err));
return;
}
window.location.href = 'confirm.html';
});
회원가입이 완료되면, AWS Cognito는 사용자의 이메일로 확인 코드를 발송한다. 사용자는 이 확인 코드를 입력하여 계정을 활성화한다. 이 과정은 ConfirmRegistration() 함수에서 이루어진다.
사용자가 회원가입 시 받은 이메일 확인 코드를 입력하면, confirmRegistration() 메서드가 호출되어 이메일을 확인한다. 이때, 사용자의 이메일과 확인 코드를 전달하여 계정 활성화를 처리한다.
확인이 성공하면 사용자는 login.html로 리다이렉트되며, 이제 로그인할 수 있다.
function ConfirmRegistration() {
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); // 사용자 풀 객체 생성
var username = document.getElementById("email").value; // 이메일 입력값
var code = document.getElementById("ConfirmCode").value; // 확인 코드 입력값
var userData = {
Username: username,
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData); // 사용자 객체 생성
cognitoUser.confirmRegistration(code, true, function(err, result) {
if (err) {
alert(err.message || JSON.stringify(err)); // 오류 발생 시 메시지 출력
return;
}
console.log('call result: ' + result); // 확인 결과 출력
window.location.href = 'login.html'; // 확인 완료 후 로그인 페이지로 이동
});
}
로그인 과정 흐름
사용자가 login.html에서 이메일과 비밀번호를 입력하고 로그인 버튼을 클릭하면 Login 함수가 실행되며, 다음의 절차가 진행된다.
사용자가 입력한 이메일과 비밀번호를 수집한다.
var username = document.getElementById("email").value;
var password = document.getElementById("password").value;
poolData에서 Cognito 사용자 풀 ID와 클라이언트 ID를 기반으로 사용자 풀 객체를 생성한다.
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
사용자의 이메일과 비밀번호를 AuthenticationDetails 객체로 감싼다. 이 객체는 사용자 자격 증명(credential)을 나타낸다.
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
인증이 성공하면 onSuccess 콜백 함수가 호출되고, 실패하면 onFailure가 호출된다.
인증이 성공하면, 반환된 result 객체에서 ID 토큰, 액세스 토큰, 갱신 토큰을 추출한다. 이 토큰들은 클라이언트가 서버에 요청을 보낼 때 사용되며, 각 토큰은 일정 시간이 지나면 만료된다. 만료된 토큰은 갱신 토큰을 통해 다시 갱신할 수 있다.
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var idToken = result.getIdToken().getJwtToken(); // ID 토큰
var accessToken = result.getAccessToken().getJwtToken(); // 액세스 토큰
var refreshToken = result.getRefreshToken().getToken(); // 갱신 토큰
console.log("idToken : " + idToken);
console.log("accessToken : " + accessToken);
console.log("refreshToken : " + refreshToken);
window.location.href = 'main.html';
},
onFailure: function(err) {
// 로그인에 실패 했을 경우 에러 메시지 표시
console.log(err);
alert("로그인 실패")
}
})
로그인 성공 후, 사용자 정보를 localStorage에 저장하고, 메인 페이지로 리다이렉트한다.
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('username', username);
window.location.href = '../main_page/index.html';
시연
login.html을 웹브라우저에서 확인해보면 아래와 같은 페이지가 나온다. 회원가입을 위해 'Sign Up' 버튼을 누르자.
그럼 signup.html 페이지로 리다이렉션되며, 아래와 같은 화면이 나온다. 회원가입을 할 이메일 정보와 비밀번호를 입력하자.
그럼 아래와 같은 이메일 인증코드를 입력하라는 페이지가 나온다. 본인의 이메일함에 들어가서 인증코드를 받아오자.
인증코드 메일은 아래와 같이 온다!
인증번호를 치고 방금 입력한 이메일과 비밀번호 정보를 가지고 로그인을 시도해보면 잘 된다.
필자는 따로 만들어놓은 웹사이트로 리다이렉팅 되도록 만들어놓았으니, 로그인이 잘 되었는지 확인하고 싶다면 리다이렉션 경로를 main.html로 바꾸면 된다. (main.html 파일은 아래에 첨부)
<!--main.html-->
<!DOCTYPE html>
<html>
<head>
<title>Main</title>
<script src="aws-sdk.min.js"></script>
<script src="aws-cognito-sdk.min.js"></script>
<script src="amazon-cognito-identity.min.js"></script>
<script src="main.js"></script>
<meta charset="UTF-8">
<link rel="stylesheet" href="main.css">
</head>
<body>
<script>main();</script>
<div class="container">
<p class="fsize">Your Login Information</p>
<input type="text" id="email" placeholder="email" readonly>
<button id="signout" hidden>Sign Out</button>
</div>
</html>