@psyrenpark/auth
v1.0.81
Published
viba auth
Downloads
79
Readme
@psyrenpark/auth
설명
인증, 토큰, email 발송등 auth에 관하여 클라이언트 (cms/web) 재사용을 위해 공통모듈로 분리
cognito라는 회원관리 시스템을 사용함으로써 패스워드등을 별도 보관하여 높은 보안 및 인증에 관한 효율적 사용성을 얻을수 있음
- react 지원
- react-native 지원
- vue.js 지원
- gatsby.JS 지원
- next.js(테스트중)
업데이트 내용
- v1.0.79
회원 가입시 이미 가입되어 있고 인증도 된경우의 에러 코드 변경
code : InvalidParameterException message : User is already confirmed. => code : UsernameExistsException message : User is already confirmed.
필요 파일
각 프로젝트의 개발 환경에 맞는 aws-exports.js 요청
React- native는
<project root>/aws-exports.js
web/cms은
<project root>/src/aws-exports.js
설정 및 설치
yarn add @psyrenpark/auth
yarn add @psyrenpark/api
yarn add @psyrenpark/storage
yarn add amazon-cognito-identity-js
yarn add aws-amplify
// react-native 추가 설치
yarn add @react-native-community/netinfo
최신 버전 적용 방법
새버전 공지시 npm에 맞는것 재설치
yarn add @psyrenpark/auth
yarn add @psyrenpark/api
yarn add @psyrenpark/storage
- 이후 최신버전으로 변경되었는지 확인할것.
DB tb_user 의 필수 컬럼
프로젝트마다 사용될 유저 테이블에 들어가야할 요소를 서버 개발자에게 요청할것 (중요)
- email or phone (필수) // 고유키
- reg_dt
- up_dt
- del_dt
- signup_type // 회원가입 방법
- user_type // 프로젝트마다 고유한 유저 타입이 있다면 정할것
- user_name
- ...
루트에서 세팅
//--------------------------------------------------
// 각 프로젝트 루트
import { Auth } from "@psyrenpark/auth";
import { Api } from "@psyrenpark/api";
import { Storage } from "@psyrenpark/storage";
import awsmobile from "./aws-exports";
Auth.setConfigure(awsmobile);
Api.setConfigure(awsmobile);
Storage.setConfigure(awsmobile);
//--------------------------------------------------
// 혹 import가 지원 되지 않는 javascript 버전에서 사용시
// aws-exports
// export default awsmobile;
module.exports = awsmobile; // 로 변경
// 각 프로젝트 루트
const { apiObject } = require("./api");
const { Api } = require("@psyrenpark/api");
const { Auth, AuthType } = require("@psyrenpark/auth");
const { Storage } = require("@psyrenpark/storage");
const awsmobile = require("./aws-exports");
Auth.setConfigure(awsmobile);
Api.setConfigure(awsmobile);
Storage.setConfigure(awsmobile);
로딩 콜백 함수 샘플
로딩에 대한 불편함을 해결하기위해 콜백형식의 함수를 주입가능하도록 되어있음
// 아래 auth 함수가 실행될때 로딩함수를 넣으면
// 시작시 isLoading => true
// 종료시 isLoading => false
// 로 주입한 함수가 호출됨
const loadingFunction = (isLoading) => {
dispatch({ type: "SET_IS_LOADING", payload: isLoading });
// or
setIsLoading(isLoading);
};
로그인 여부 체크
- 이미 로그인한적이 있는지 체크하는 함수
- 이 과정을 한 후에 인증이 필요한 api 사용가능
- 비동기, 콜백 방식중 필요한 방식을 선택하여 사용할 것
- cms라면 루트 라우팅에서 로그인창을 띄울지 말지를 결정하는 프로세스(중요)
import { Auth } from "@psyrenpark/auth";
//--------------------------------------
// 비동기 방식
const isCheckToLoginFunction = async (props) => {
try {
var auth = await Auth.currentSession();
// 로그인 상태
return true;
} catch (error) {
// 로그아웃 상태
return false;
}
};
//--------------------------------------
// 콜백 방식
const checkToLoginFunction = async (props) => {
Auth.currentSessionProcess(
async (data) => {
// 성공처리 및 로그인 된 상태
// 자동 로그인 필요
// 서버에서 유저 정보 필요시 여기서 비동기로 가져올것
},
(error) => {
// 실패처리 및 로그아웃 상태
// 로그인 화면으로 이동 필요
},
loadingFunction
);
};
특정 그룹 포함 여부 체크
- 이 유저가 특정 그룹에 속해있는지 체크하는 함수
- 이로직은 토큰안에 유저 그룹을 포함시키는 방법으로써 각 프로젝트마다 다르니 필요시 요청 (높은 보안 요구시)
- user, admin, manager 등이 있을수 있다. (기능필요시 요청 할것)
import { Auth } from "@psyrenpark/auth";
//--------------------------------------
// 비동기 방식
const isIncludeGroupFunction = async (props) => {
try {
var isIncludeGroup = await Auth.isIncludeGroup("admin");
if (!isIncludeGroup) {
await Auth.signOut();
// 로그아웃하고 홈으로 리다이렉트 및 유저 관련 리덕스 초기화
return;
}
// 정상로직
} catch (error) {}
};
cms등 특정 유저 로그인을 막기위한 처리 아닌경우 강제 로그아웃
- cms나 특정 유저만 사용가능 해야할 경우
- 로그인후 await Auth.currentSession() 로 로그인체크
- Auth.isIncludeGroup("특정 그룹 key"); 로 토큰안에 key가 포함되어 있는지 체크
- false 일경우 강제 로그아웃 및 유저 정보 관련된 정보 초기화 할것
const isIncludeGroupFunction = async (props) => {
try {
var auth = await Auth.currentSession();
console.log("checkAuth -> auth", auth);
var isAdmin = await Auth.isIncludeGroup("admin");
console.log("checkAuth -> isAdmin", isAdmin);
if (!isAdmin) {
// 로그아웃후 리덕스나 내부 저장소 날리는 작업필요
await Auth.signOut();
//clear()
return;
}
// 성공후 자기 관리자 정보 가져오기
} catch (error) {
// 에러시 강제 로그아웃 후 리덕스나 내부 저장소 날리는 작업필요
await Auth.signOut();
//clear()
}
};
회원가입
- 회원가입 함수
- [선인증] 필요시 (휴대폰 인증, pass등) 화면 플로우에 맞게 클라이언트에서 미인증 서버 api 호출을
진행후 이결과를 cognitoRegComm안에 포함시켜준다. (중요) - 현재 이메일 또는 전화번호 회원가입만 지원한다. (아이디등은 차후)
- 프로젝트에 따라 이메일을 자동 인증하는 경우와 인증을 체크하는 경우 2가지가 있다.
- [후인증] 필요시 이메일 인증을 하는경우 이메일에 발송된 코드 입력창으로 이동시켜줘야한다.
- [자동인증] 되는 경우 성공시 로그인화면 또는 자동로그인 화면으로 이동해야한다.
- email은 소문자로 변경해주며, 앞뒤 트림도 해줘야한다.
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const signUpFunction = async (formData) => {
Auth.signUpProcess(
{
email: formData.userEmail,
password: formData.userPassword,
authType: AuthType.EMAIL,
lang: "en", // 가입시 유저 선택 언어
cognitoRegComm: {
// 서버에서 회원가입시 처리할 필요 파라미터를 첨부한다. 위의 [DB tb_user 의 필수 컬럼] 참고
// 패스워드 같은 정보는 포함시키지 않는다.
name: formData.userName,
age: formData.userAge,
// ...
},
},
async (data) => {
// 성공처리 및 회원가입 성공
// 자동인증인 경우 로그인 화면으로 이동 및 자동로그인 필요
// 인증을 체크하는 경우 인증코드 입력창 화면으로 이동 필요
},
(error) => {
// 회원가입 실패
},
loadingFunction
);
};
회원가입 후 인증 확인 함수
- 회원인증 함수
- 인증을 체크하는 경우 코드입력 화면에서 코드값을 넣어 아래 함수를 실행한다.
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const confirmSignUpFunction = async (formData) => {
Auth.confirmSignUpProcess(
{
// 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
email: formData.userEmail,
password: formData.userPassword,
authType: AuthType.EMAIL,
code: formData.userCode, // 이메일에 있는 인증코드
},
async (data) => {
// 성공처리 및 인증 성공
// 로그인 화면이나 자동로그인 필요
},
(error) => {
// 실패처리,
// 인증코드가 틀렸을경우 및 만료 된경우
// 인증 메일 재전송이 필요하다.
},
loadingFunction
);
};
회원가입 후 인증 재전송 함수
- 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const resendSignUpFunction = async (formData) => {
Auth.resendSignUpProcess(
{
// 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
email: formData.userEmail,
},
async (data) => {
// 성공처리
// 성공적으로 인증 메일 재전송
},
(error) => {
// 실패처리,
},
loadingFunction
);
};
로그인
- 로그인 함수
- 로그아웃 상태일 경우 이 과정을 한후에 인증이 필요한 api 사용가능
- 인증안하고 종료후 다시 로그인할경우 3번째 파라미터 부분 참고할것
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const signInFuntion = async (formData) => {
Auth.signInProcess(
{
email: formData.userEmail,
password: formData.userPassword,
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리 및 로그인 된 상태
// 서버에서 유저 정보 필요시 여기서 비동기로 가져올것
// 서버로 자기 정보 가져오는 api를 호출해야한다.
},
async (data) => {
// 회원가입은 되었으나 인증을 안했을경우
// 인증 안하고 강제 종료 했거나 화면 나갔을경우 타는 함수
// 인증 화면으로 이동필요
// 자동으로 인증 메일 재발송됨
},
(error) => {
// 실패처리,
},
loadingFunction
);
};
소셜 로그인 관련
- 소셜 로그인 라이브러리 추가후 로그인후 나오는 인증 정보중
- email, password는 고유 아이디, name은 있으면 넣고 없으면 디폴트로 넣어줄것
- password의 고유 아이디는 앱, 웹 에서 서로 같아야 각각에서 같은 계정으로 로그인이 가능함. (중요)
- 앱을 여러개로 출시할경우 소셜 유형에 따라 (유저용앱, 관리자용앱) 고유 아이다가 서로 다를수 있으니 주의할것 (예씨 카카오톡)
- 각 라이브러리(웹, 앱) 로그인후 나오는 토큰 분해후 잘 체크해볼것
- user_info 필수 param / [email, password, name]
- 소셜특징중 이메일을 알아낼수 없는경우 고유한 키를 조합할것 예시 (고유키+@[소셜].com (중요)
- 암호화가 필요할경우 isCrypto:true (테스트중)
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const signInProcess = async (
params = {},
successCallback,
failCallback,
loadingCallback,
) => {
if (loadingCallback) {
loadingCallback(true);
}
try {
// 중요
// 쇼설 로그인 라이브러리 추가후 로그인후 나오는 인증 정보중
// email, password는 고유 아이디, name은 았으면 넣고 없으면 디폴트로 넣어줄것
// user_info 필수 param / email, password, name
var user_info = await appleLogin(); //or await googleLogin();
// var user_info =
// {
// ...userInfo.user, // 디비에 넣을 필요정보들
// email: userInfo.user.email, // 필수
// password: userInfo.user.id, // 필수
// name: user.givenName, // 필수
// }
} catch (error) {
if (loadingCallback) {
loadingCallback(false);
}
if (failCallback) {
failCallback(error);
return;
}
await Auth.signUpProcess(
{
email: user_info.email,
password: user_info.password,
authType: AuthType.APPLE, // or authType: AuthType.GOOGLE, // enum에 없을경우 요청할것
lang: params.lang ? params.lang : 'en',
cognitoRegComm: {
...user_info,
...params,
},
},
async (data) => {
if (successCallback) {
await successCallback(data);
}
},
(error) => {
if (failCallback) {
failCallback(error);
}
},
(flag) => {
// if (!flag && loadingCallback) {
loadingCallback(flag);
// }
},
);
};
비밀번호 분실 1단계
- 로그아웃 상태에서 비밀번호를 모를경우
- 이메일 인증후 새로운 패스워드 설정이 가능하다.
- 비밀번호 변경은 이메일 타입만 가능하다. 그외에는 비활성
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const forgotPasswordFunction = async (formData) => {
Auth.forgotPasswordProcess(
{
email: formData.userEmail,
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리 및 인증 메일 발송됨
// 패스워드 분실 2단계로 이동
},
(error) => {
// 실패처리,
},
loadingFunction
);
};
비밀번호 분실 2단계
- 유저가 가입한 이메일로 인증메일 발송됨
- 발송된 유저 코드 입력 와 새로운 패스워드 입력 필요
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const confirmForgotPasswordFunction = async (formData) => {
Auth.confirmForgotPasswordProcess(
{
// 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
email: userEmail,
code: formData.userCode,
newPassword: formData.userPassword, //새로 지정할 newPassword 이다.
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리 및 패스워드 변경
// 성공하면 자동으로 로그인 되니
// 바로 메인으로 이동하면됨
},
(error) => {
// 코드 잘못 입력
},
loadingFunction
);
};
비밀번호 분실 2단계 인증 코드 재전송
- 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const resendForgotPasswordFunction = async (formData) => {
Auth.resendForgotPasswordProcess(
{
// 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
email: formData.userEmail,
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리
},
(error) => {
// 실패처리,
},
loadingFunction
);
};
로그인 상태에서 패스워드 변경
- 로그인된 상태에서 비밀번호를 변경할 경우
- 여기서는 메일이 발송되지않는다.
- 이미 로그인된 상태에서 실행해야한다.
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const changePasswordFunction = async (formData) => {
Auth.changePasswordProcess(
{
// 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
email: formData.userEmail,
oldPassword: formData.userOldPassword,
newPassword: formData.userNewPassword,
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리
// 정상적으로 패스워드 변경
// 로그아웃 시켜 로그인 화면으로 이동시키는 편이 좋음
},
(error) => {
// 실패처리
},
loadingFunction
);
);
};
로그아웃
- 로그아웃을 하면 토큰이 발급되지 않는다
- isCheckToLoginFunction or checkToLoginFunction에서 실패남
- 로그아웃후 로그인화면으로 강제 이동필요(중요)
- 소셜 로그인일경우 성공 처리 사이에 소설 로그인을 넣어서 처리한다.
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const signOutFunction = async () => {
Auth.signOutProcess(
{
authType: AuthType.EMAIL,
},
async (data) => {
// 성공처리 및 로그아웃
// 리덕스나, context의 저장된 정보 초기화 필요
// 그후 로그인 화면으로 이동
},
(error) => {
// 실패처리,
},
loadingFunction
);
};
유저 탈퇴 관련
- 실제 amplify에서 삭제 기능은 없으므로 서버딤딩지에게 유저 삭제 api 기능 요청할것
- 서버 담장자는 express-lib-doc.md 참고
- 클라이언트는 이 api가 성공후 꼭 반드시 로그아웃후 redux, context안의 유저 정보등 정리 할것
- 이후 web, cms, react-native등 이후 강제 로그인화면으로 이동 필요
에러 코드 정의
- 실제 amplify에서 주는 코드 외
- 이 라이브러리에서 던져 주는 에러코드도 있다
- 이 목록에 없는 코드 발견시 제보 바람
// amplify에서 error
error.code === "UsernameExistsException" // email등이 중복되었을 경우
error.code === "InvalidParameterException" // 필요 파라미터가 부족할경우
error.code === "NotAuthorizedException" // 인증 권한이 없을경우
error.code === "UserNotFoundException" // 헤딩 유저가 없을 경우
error.code === "CodeMismatchException" // 인증 코드가 틀렸을경우
error.code === "LimitExceededException" // 인증시도 초과시
error.code === "UserLambdaValidationException" // 개발시난 에러 (api 담당자에게 버그수정 요청)
// custom error
error.code === "NotSignedException" // 로그인 되지 않았을 경우
error.code === "NotImplementedException" // 아직 미구현 일경우 (쇼셜 관련 기능)
error.code === "NotSupportAuthTypeException" // 아직 지원되지 않는 쇼셜 타입
error.code === "SamePasswordException" // 패스워드 변경시 이전패스워드와 현재 패스워드가 같을경우
// 에러 코드 처리
// - error.code로 검사할것 사용할것
(error) => {
// 실패처리,
console.log("changePasswordFunction -> error.code", error.code);
console.log("changePasswordFunction -> error.name", error.name);
console.log("changePasswordFunction -> error.message", error.message);
alert(error.message);
},
// 패스워드를 잘못 입력했을경우 참고 자료
- https://github.com/aws-amplify/amplify-js/issues/1234
- ( 10번 이내 )
code : NotAuthorizedException
message : "Incorrect username or password"
- ( 10번 이상 )
code : NotAuthorizedException
message : "Password attempts exceeded"
// 무차별 대입 공격으로부터 보호
- https://stackoverflow.com/questions/37732970/how-aws-cognito-user-pool-defends-against-bruteforce-attacks
`로그인 시도는 5회 실패할 수 있습니다. 그 후 우리는 1초에서 시작하여 각 시도가 실패한 후 최대 약 15분까지 두 배로 증가하는 시간으로 임시 잠금을 시작합니다. 임시 잠금 기간 동안의 시도는 무시됩니다. 임시 잠금 기간이 지난 후 다음 시도가 실패하면 마지막 시간의 두 배 기간으로 새로운 임시 잠금이 시작됩니다. 시도하지 않고 약 15분을 기다리면 임시 잠금도 재설정됩니다. 이 동작은 변경될 수 있습니다.`
이메일 전송 양식 설명
변경필요시 아이콘과 맨트를 적어서 서버관리자에게 전달할것
다국어처리 필요시 서버관리자에게 전달 할것
이메일전송시 일단 기본적 양식으로 날라감
예시 맨트 SBAA0404: "안녕하세요",
SBAA0405: "싸이페어에 오신 것을 환영합니다",
SBAA0406: "하단의 인증번호를 싸이페어 화면에 입력해주세요.",
SBAA0407: "이메일 인증번호",
SBAA0408: "(주)싸이페어",
SBAA0409: "",
SBAA0410: "이 메일은 (주)싸이페어에서 발송되었습니다.", \
추가로 이메일에 붙을 아이콘 첨부바람
예시 icon https://sf-prod-file.s3-ap-northeast-1.amazonaws.com/public/email/img/icon/square_logo_Blck_400.png
완성 예시 https://sf-prod-file.s3-ap-northeast-1.amazonaws.com/public/email/email-example.jpg
react-hook-form 예시
- 필수는 아니나 사용하면 편리
- form속성같이 auth화면에서 사용하면 편리하게 개발 가능
- 필수 속성, 입력값 정규식 체크 등이 편리하게 사용가능
- 자세한 내용은 https://react-hook-form.com/ 참고
//-------------------------------------------
// react 버전
import React, { useState, useEffect, useContext } from "react";
import { Grid, Checkbox, TextField } from "@material-ui/core";
//-------------------------------------------
// redux
import { useDispatch, useSelector } from "react-redux";
//--------------------------------------------------
// auth
import {
Auth,
CurrentAuthUiState,
AuthType,
UserState,
} from "@psyrenpark/auth";
//--------------------------------------------------
// hook
import { useForm, useWatch, Controller } from "react-hook-form";
import { useTypedController } from "@hookform/strictly-typed";
//--------------------------------------------------
export const SignInComponent = (props) => {
const reducer = useSelector((state) => state.reducer);
const dispatch = useDispatch();
// useForm 속성
const {
register,
handleSubmit,
watch,
errors,
formState,
control,
trigger,
} = useForm();
// material ui 사용시
const TypedController = useTypedController({ control });
const { toggle } = watch();
const signInFuntion = async (data) => {
console.log("signInFuntion -> data", data);
var userEmail = data.userEmail;
var userPassword = data.userPassword;
};
return (
<Grid className="sign_in sign">
{/* Input 사용시 */}
<Grid className="input_wrap">
<input
name="userEmail"
ref={register({
required: true,
pattern: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/,
})}
type="text"
placeholder="이메일 주소"
/>
</Grid>
{/* material ui TextField 사용시 */}
<Grid className="input_wrap">
<TypedController
name="userEmail"
defaultValue=""
render={(props) => <TextField {...props} />}
rules={{
required: true,
pattern: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/,
}}
/>
</Grid>
{errors?.userEmail && (
<p className="warning">email 정보가 올바르지 않습니다.</p>
)}
<Grid className="input_wrap">
<input
name="userPassword"
ref={register({
required: true,
pattern: /(?=.*\d{1,50})(?=.*[~`!@#$%\^&*()-+=]{1,50})(?=.*[a-zA-Z]{2,50}).{8,50}$/,
})}
type="password"
placeholder="비밀번호"
onKeyUp={() => {
if (window.event.keyCode === 13) {
handleSubmit(signInFuntion);
}
}}
/>
</Grid>
{errors.userPassword && (
<p className="warning">password 정보가 올바르지 않습니다.</p>
)}
<button
type="button"
className={
watch("userEmail", false) && watch("userPassword", false)
? "btn_move on"
: "btn_move"
}
// className={"btn_move"}
onClick={handleSubmit(signInFuntion)}
>
다음
</button>
</Grid>
);
};