이 포스팅에서는 AWS의 Cognito user pool을 사용해서 React app에서 사용자 인증 과정(sign-up, sign-in, sign-out)을 구현하고자 한다.
여기서 연동 자격 증명 공급자를 선택하면 소셜 로그인도 설정할 수 있다.
암호 정책은 기본값으로 사용.
사용자 지정으로 하면 암호 요구 사항을 custom 할 수 있음.
멀티 팩터 인증은 없음으로 설정하고, 사용자 계정 복구는 default로 사용.
default로 설정
필수 속성은 name만 선택. 필수 속성 아래에는 사용자 지정 속성이 있는데, 여기에서 필수 속성에 있지 않은 custom 속성을 설정해서 사용할 수 있다.
Cognito를 사용하여 이메일 전송.
사용자 풀 이름을 설정하고, 앱 클라이언트를 설정한다.
앱 클라이언트가 있어야 react에서 사용할 수 있으니 꼭 생성한다.
npm install aws-amplify @aws-amplify/ui-react
npm install aws-amplify
리액트 앱에서 amplify auth를 설정하려면 user pool의 ID, ARN, 클라이언트 ID가 필요하다.
Cognito 콘솔에서 찾아서 복사.
리액트 앱에서 awsconfig.js로 만들어서 사용.
export default {
Auth: {
region: "YOUR-REGION",
userPoolId: "YOUR-POOL-ID",
userPoolWebClientId: "YOUR-CLIENT-ID"
}
}
전체적인 리액트 파일의 구조는 이렇게 되어 있다.
import "./App.css";
import React from "react";
import AuthView from "./views/AuthView";
export default function App() {
return (
<>
<AuthView />
</>
);
}
Auth process 관련 함수를 정의하고, Authenticator 를 사용해서 UI를 구현했다.
Authenticator를 사용하면 바로 로그인 UI가 구현된다.
하지만, 백엔드 코드가 작성되어 있지 않아서 제대로 동작하지 않는다. 위에서 로그인 관련 함수를 작성해둬야 한다. 이 함수들은 공식 문서에 코드가 나와 있으며, 자신의 user pool에 맞춰서 변경하면 된다.
return에서 main 코드 안에는 로그인 후에 볼 수 있는 화면을 설정한다.
import React from "react";
import Amplify, { Auth } from "aws-amplify";
import { Authenticator, Button } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import components from "../theme/components";
import formFields from "../theme/formFields";
import awsconfig from "../auth/awsconfig";
Amplify.configure(awsconfig);
export default class AuthView extends React.Component {
constructor() {
super();
this.state = {
username: "",
email: "",
password: "",
new_password: "",
code: "",
stage: "SIGNIN",
cognito_username: "",
};
}
componentDidMount() {
Auth.currentAuthenticatedUser()
.then((user) => {
console.log(user);
this.setState({ stage: "SIGNEDIN", cognito_username: user.username });
// console.log(user.signInUserSession.accessToken.jwtToken);
})
.catch(() => {
console.log("Not signed in");
});
}
signUp = async () => {
let self = this;
try {
const user = await Auth.signUp({
username: self.state.email,
password: self.state.password,
attributes: {
email: self.state.email, // optional
name: self.state.username,
},
});
self.setState({ stage: "VERIFYING" });
} catch (error) {
console.log("error signing up:", error);
}
};
signOut = async () => {
try {
await Auth.signOut();
window.location.href = "/";
} catch (error) {
console.log("error signing out: ", error);
}
};
signIn = async () => {
let self = this;
try {
const user = await Auth.signIn({
username: self.state.email,
password: self.state.password,
});
this.setState({ stage: "SIGNEDIN", cognito_username: user.username });
} catch (error) {
console.log("error signing in", error);
if (error.code === "UserNotConfirmedException") {
await Auth.resendSignUp(self.state.email);
this.setState({ stage: "VERIFYING" });
}
}
};
confirm = async () => {
let self = this;
console.log(self.state.code);
let username = self.state.email;
let code = self.state.code;
try {
await Auth.confirmSignUp(username, code);
//바로로그인?
this.signIn();
} catch (error) {
console.log("error confirming sign up", error);
}
};
changePassword = async () => {
let self = this;
Auth.currentAuthenticatedUser()
.then((user) => {
return Auth.changePassword(
user,
self.state.password,
self.state.new_password
);
})
.then((data) => console.log(data))
.catch((error) => console.log(error));
};
changePasswordForgot = async () => {
let self = this;
Auth.forgotPasswordSubmit(
self.state.email,
self.state.code,
self.state.new_password
)
.then((data) => {
console.log("SUCCESS");
})
.catch((error) => {
console.log("err", error);
});
};
resendCode = async () => {
let self = this;
try {
await Auth.resendSignUp(self.state.email);
console.log("code resent succesfully");
} catch (error) {
console.log("error resending code: ", error);
}
};
sendCode = async () => {
let self = this;
Auth.forgotPassword(self.state.email)
.then((data) => {
console.log(data);
})
.catch((error) => console.log(error));
};
render() {
return (
<Authenticator
loginMechanisms={["email"]}
variation="modal"
formFields={formFields}
components={components}
>
{({ signOut, user }) => (
<main>
<h1>Hello {user.username}</h1>
<Button onClick={signOut}>Sign out</Button>
</main>
)}
</Authenticator>
);
}
}
여기 코드를 보면 formFields와 components를 정의해두었다. 이 옵션은 필수는 아니고 UI를 커스텀한 것이다. 공식 문서를 참조하면 된다.한 파일 안에 작성해도 되지만 길어져서 분리해서 두었다.
<Authenticator
loginMechanisms={["email"]}
variation="modal"
formFields={formFields}
components={components}
>
components는 로그인 화면의 전체적인 배치를 custom 할 수 있게 한다. Header, Footer 등을 정의할 수 있다.
import {
useAuthenticator,
useTheme,
View,
Image,
Text,
Heading,
Button,
} from "@aws-amplify/ui-react";
import logo from "../assets/images/_logo_white.png";
const components = {
Header() {
const { tokens } = useTheme();
return (
<View textAlign="center" padding={tokens.space.large}>
<Image alt="Amplify logo" src={logo} />
</View>
);
},
Footer() {
const { tokens } = useTheme();
return (
<View textAlign="center" padding={tokens.space.large}>
<Text color={`${tokens.colors.neutral["80"]}`}>
© All Rights Reserved
</Text>
</View>
);
},
SignIn: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Sign in to your account
</Heading>
);
},
Footer() {
const { toResetPassword } = useAuthenticator();
return (
<View textAlign="center">
<Button
fontWeight="normal"
onClick={toResetPassword}
size="small"
variation="link"
>
Reset Password
</Button>
</View>
);
},
},
SignUp: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Create a new account
</Heading>
);
},
Footer() {
const { toSignIn } = useAuthenticator();
return (
<View textAlign="center">
<Button
fontWeight="normal"
onClick={toSignIn}
size="small"
variation="link"
>
Back to Sign In
</Button>
</View>
);
},
},
ConfirmSignUp: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Enter Information:
</Heading>
);
},
Footer() {
return <Text>Footer Information</Text>;
},
},
SetupTOTP: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Enter Information:
</Heading>
);
},
Footer() {
return <Text>Footer Information</Text>;
},
},
ConfirmSignIn: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Enter Information:
</Heading>
);
},
Footer() {
return <Text>Footer Information</Text>;
},
},
ResetPassword: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Enter Information:
</Heading>
);
},
Footer() {
return <Text>Footer Information</Text>;
},
},
ConfirmResetPassword: {
Header() {
const { tokens } = useTheme();
return (
<Heading
padding={`${tokens.space.xl} 0 0 ${tokens.space.xl}`}
level={3}
>
Enter Information:
</Heading>
);
},
Footer() {
return <Text>Footer Information</Text>;
},
},
};
export default components;
formFiels는 각 화면에서 textfield 같은 부분을 커스텀 할 수 있는데, label이나 placeholder 등을 설정할 수 있다.
const formFields = {
signIn: {
username: {
labelHidden: false,
placeholder: "Enter your email",
},
},
signUp: {
email: {
// label: "Email",
// labelHidden: false,
placeholder: "Enter your email",
order: 1,
},
name: {
// label: "Username:",
// labelHidden: false,
placeholder: "Enter your name",
order: 2,
},
password: {
// labelHidden: false,
// label: "Password:",
placeholder: "Enter your Password:",
isRequired: false,
order: 3,
},
confirm_password: {
// labelHidden: false,
// label: "Confirm Password:",
order: 4,
},
},
forceNewPassword: {
password: {
labelHidden: false,
placeholder: "Enter your Password:",
},
},
resetPassword: {
username: {
labelHidden: false,
placeholder: "Enter your email:",
},
},
confirmResetPassword: {
confirmation_code: {
labelHidden: false,
placeholder: "Enter your Confirmation Code:",
label: "New Label",
isRequired: false,
},
confirm_password: {
labelHidden: false,
placeholder: "Enter your Password Please:",
},
},
setupTOTP: {
QR: {
totpIssuer: "test issuer",
totpUsername: "amplify_qr_test_user",
},
confirmation_code: {
labelHidden: false,
label: "New Label",
placeholder: "Enter your Confirmation Code:",
isRequired: false,
},
},
confirmSignIn: {
confirmation_code: {
labelHidden: false,
label: "New Label",
placeholder: "Enter your Confirmation Code:",
isRequired: false,
},
},
};
export default formFields;
색은 커스텀하지 않았고, 로고만 변경해주었다.
Create Account 버튼을 누르면, 입력한 이메일 주소로 verification code가 발송되고, 코드를 입력하면 user pool에서 확인된 사용자로 바뀌고 계정 생성이 완료된다.계정을 생성하고 cognito 콘솔에서 생성된 사용자를 확인할 수 있다.
계정 생성이 완료되면 자동으로 로그인이 되고, user name과 sign-out 버튼을 볼 수 있다.