이 포스팅에서는 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 버튼을 볼 수 있다.