이번에 플젝을 next 를 쓰는 플젝을 진행했는데 다른건 그냥 만들면 됐는데 처음 가장 발목잡았던 next auth...

설정다하면 편하기는 편한데 설정이 복잡하다...

 

우선 next 디렉토리 구소를 api 형식이다. pages 라면 구글링해보면 다른 예제가 많아서 구글링을 추천한다.

 

https://next-auth.js.org/

 

NextAuth.js

Authentication for Next.js

next-auth.js.org

 

여기서 npm install 해주고 폴더구조를 우선

api/auth/[...nextauth]/route.js 를 정한다. ts 안쓰는 이유는 내가 안쓰는거까지도 타입다 지정해야하고 너무 불편하다

구글 애플/ 네이버 카카오 리턴이 다른데 콜백에서 접근할때 없는거니까 접근안된다고 에러까지 뜬다..

이런 에러 헨들링 가능하면 ts 를 추천한다.

 

 

 

import NextAuth, { AuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import AppleProvider from "next-auth/providers/apple";
import KakaoProvider from "next-auth/providers/kakao";
import NaverProvider from "next-auth/providers/naver";
import CredentialsProvider from "next-auth/providers/credentials";
import { BASE_URL, JWT_SECRET } from "@/util/path";
import jwt from 'jsonwebtoken';

import { SignJWT } from "jose";
import { createPrivateKey, KeyObject } from "crypto";

const authOptions = {

	session: {
		strategy: "jwt",
		maxAge: 60 * 60 * 24 * 7, // 31일
		updateAge: 60 * 60 * 24, // 1일
	},
	cookies: {
		pkceCodeVerifier: {
			name: "next-auth.pkce.code_verifier",
			options: {
				httpOnly: true,
				sameSite: "none",
				path: "/",
				secure: true,
			},
		},
	},
	pages: {
		signIn: "/next/login", // 로그인 페이지
		signOut: "/next/signout", // 로그아웃 페이지
		error: "/next/api/auth/error", // 오류 페이지
		verifyRequest: "/next/api/auth/verify-request", // 이메일 확인 페이지
	},
	providers: [
		CredentialsProvider({
			name: "Credentials",
			credentials: {
				username: { label: "Username", type: "text" },
				password: { label: "Password", type: "password" },
			},
			async authorize(credentials, req) {

				// 로그인 인증 처리 추가해야함
				
				const response_j = await response.json();

				let user = { id: null, name: null, email: null };

				if (response_j?.result) {
					user = { id: credentials.username, name: null, email: response_j._email };
				}

				if (user.id) {
					return user;
				} else {
					return null;
				}
			},
		}),

		GoogleProvider({
			clientId: process.env.GOOGLE_CLIENT_ID,
			clientSecret: process.env.GOOGLE_KEY,
		}),
		AppleProvider({
			clientId: process.env.APPLE_ID,
			clientSecret: await getAppleToken(),
		}),
		KakaoProvider({
			clientId: process.env.KAKAO_API_KEY,
			clientSecret: process.env.KAKAO_SCRIPT_KEY,
		}),
		NaverProvider({
			clientId: process.env.NAVER_API_KEY,
			clientSecret: process.env.NAVER_CLIENT_SECRET,
		}),


	],
	secret: process.env.NEXTAUTH_SECRET,
	callbacks: {
		async signIn({ user, account, profile, email, credentials, context }) {
			console.log("======signIn======");

			if (account.provider === "apple" || account.provider === "naver" || account.provider === "kakao" || account.provider === "google") {
				// 1. 가입 유무 체크  user.id
				
				const response_j = await response.json();

				if (response_j.result) {
					// 3. 가입 되어있으면 로그인 완료
					// console.log("regiCheck > response_j:: ", response_j)
				} else {

					// 2. 가입 안되면 간편가입으로 (필요 데이터 넘기기)
					// 전화번호, 아이디, 이메일, 이름, 닉네임, 소셜로그인,
					let nickname = null
					let phonenumber = null

					if (account.provider === "kakao") {
						nickname = profile.properties.nickname
						phonenumber = profile.kakao_account.phone_number
						if (phonenumber) {
							phonenumber = phonenumber.replace('+82', '0').replace(/[\s-]+/g, "").replace(/[^0-9]*/s, '');
						}
					} else if (account.provider === "naver") {
						nickname = profile.response.nickname
						phonenumber = profile.response.mobile
						if (phonenumber) {
							phonenumber = phonenumber.replace('+82', '0').replace(/[\s-]+/g, "").replace(/[^0-9]*/s, '');
						}
					}
					// 여기서 로그인 안되어있으니까 다른 페이지로 이동					

                }

			}

			if (account.provider === "credentials") {

			}

			return true;
		},
		async jwt({ token, trigger, session, user}) {
			console.log("======jwt======");

			let uToken = null;
			if (token?.uId !== null && token?.uId !== undefined) {
				uToken = jwt.sign({
					username: token.username,
				}, JWT_SECRET, { expiresIn: '5m' });
				token.uToken = uToken;
			}

			if (token?.sub) {
				console.log("======jwt setUserData======");
				

				const response_j = await response.json();

				// console.log("jwt > > response_j: ",response_j)
				if (response_j?.uData) {
					// 여기서 로그인 성공시 추가 테이터 지정 
                    token.username = response_j?.uData?.username;
				}
			}

			return token;
		},
		async session({ session, token, trigger, newSession }) {
			// console.log("======session======");

			// 토큰으로 지정한애들이 token. 안에 다 들어있어서 세션에 추가해야지 우리가 접근할수있다
			if (token.username !== undefined) {
				session.user.username = token.username;
			}

			return session;
		},

	},
};

// 이건 애플로그인 할때 필요한건데 그냥 넣자
async function getAppleToken() {
	const key = await process.env.APPLE_SECRET;

	const privateKey = createPrivateKey({
		key: key,
		format: "pem",
		type: "pkcs8",
	});

	const appleToken = await new SignJWT({})
		.setAudience("https://appleid.apple.com")
		.setIssuer(process.env.APPLE_TEAM_ID)
		.setIssuedAt(Math.floor(Date.now() / 1000))
		.setExpirationTime(Math.floor(Date.now() / 1000) + 3600 * 2)
		.setSubject(process.env.APPLE_ID)
		.setProtectedHeader({
			alg: "ES256",
			kid: process.env.APPLE_KEY_ID,
		})
		.sign(privateKey);

	return appleToken;
}


const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

 

 

이렇게 코드를 크게 짤수있다. 쿠키에 pkceCodeVerifier 이거는 애플로그인할때 필요하다. 중요 코드는 다빼서 알아서 추가해서 쓰자

pages 에 /next 를 다 붙이는 이유는 

 

next config 에 

    basePath: '/next',
    assetPrefix: '/next/',

이걸 추가해서 넣었다 

 

그리고 나처럼 로그인 다되고 user 를 뜯어고치고 싶으면 app/type/next-auth.d.ts 를 추가하자

 

import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {

    // 추가할 세션 항목
    user: {
      username: string | null,
    }
    // & DefaultSession["user"] // 기존 세션 항목 유지
  }
  interface User {

  }
}

 

이렇게 추가하면 된다

 

그리고 api 의 루트 layout 구조도 변경해야하는데

import React from "react";
import "./globals.css";

import { NextAuthProvider } from "./providers";

export default function RootLayout({ children,}: { children: React.ReactNode; }) {
  return (
    <html >
      <body>
        <div className=" bg-white">
          <NextAuthProvider>
            {children}
          </NextAuthProvider>
        </div>
      </body>
    </html>
  );
}


이렇게 수정하고 NextAuthProvider 는 

"use client";

import { SessionProvider } from "next-auth/react";
import React from "react";

type Props = {
  children?: React.ReactNode;
};

export const NextAuthProvider = ({ children }: Props) => {
  return <SessionProvider basePath="/next/api/auth">{children}</SessionProvider>;
};

이렇게 설정하자 

 

env 설정은 다른거는 다 구글링하면서 알아서 하겠지만

NEXTAUTH_SECRET

JWT_SECRET

NEXTAUTH_URL="만약에 나처럼 url 건들었음 https://도메인명/next/api/auth/ 이런식으로 지정해야한다 "  

APPLE_SECRET="-----BEGIN PRIVATE KEY-----\n여기에 키 내용 처음 열었을때 줄바꿈마다\n 을 붙여야함\n-----END PRIVATE KEY-----\n"

이거는 꼭 확인해서 적어주자 

리액트를 배우고 있어서 아주아주 깔끔하지 않지만 숫자만 받는데 

type="number" 로만 처리하는데 신경쓰여서 한번 만들어 보자 하고 만들어 보았다

 


    const OnlyNumber = (e, predata) => {
        const curType = e.nativeEvent.inputType;
        const curValue = e.nativeEvent.data;

        if (curValue !== "" && curValue !== null) {
            const newValue = curValue.replace(/[^0-9]/g, '');
            setMovieYear(predata + newValue)
        } else if (curType === "deleteContentBackward") {
            const delData = predata.slice(0,-1);
            setMovieYear(delData)
        }
    };

해당함수를 선언해두고 


     <input type="text" value={movieYear} placeholder= "숫자만 입력해주세요" onChange={e => OnlyNumber(e, movieYear)}>

return 문의 html 코드는 이렇게 사용한다 

 

 


개발 스토리

코드를 쓰다보면 함수가 아무리 복잡하고 어려워도 함수를 작동하는 방식은

간결하고 깔끔해야만 한다는 생각으로 개발중이다 

 

코드를 보면 알겠지만 깔끔하지 않다 nativeEvent안에 값을 받아서 replace 돌리고

값을 세팅해주는 방식인데 이전의 값을 더해주지 않으니 초기화 되어

1자리 이상값세팅이 안되어서 이전의 값도 불러와서 이전의 문자열에 더해주는 방식이다

 

그랬더니 끝난줄알았는데 지우기 키를 입력하니 콘솔이 터졌다 가져온 값이 null 이라서

replace 함수가 터져버렸다

 

콘솔로 e를 찍어서 지우기 키입력의 고유값을 찾아봤다 그중에 

inputType: "deleteContentBackward" 이게 눈에 띄었다 다른 키를 입력하면서 다른 키는 어떻게 받나 확인했다

inputType: "insertText" 다른 키들은 다 이런식이었다

 

그럼 이값으로 if문을 마저 짜자

e.nativeEvent.inputType; 를 가져오고 else if 로 구분까지는 했다
 
그런데 문자열 뒤에 하나를 잘라서 다시 set 하고 이게 맞는건지 의문이 들었지만 지금은 REACT를 배우고있고 js함수 방식으로 적용하는 방식을 잘모른다..
그러니 그냥 이가 없으면 잇몸으로라도 악으로 깡으로 만들어보자
slice(0,-1);
함수를 사용해서 일단 뒤에 문자열을 지우는데는 성공했다
 

느낀점

html5 매뉴얼 만들어주는 분들이 조금은 원망스럽다 e를 사용할수있는
number와 오로지 숫자만 입력되는 OnlyNumber를 지원해줬더라면 이런 고생은 하지 않았을것이다
그래도 삽질을 하면서 생각하는대로 잘풀려서 함수를 만드는데 성공했다.

 

1. JS

import './App.css';
import { useState, useEffect } from 'react';
import Data from './components/Data'; // 디렉토리 구조

function App() {

  const data = [
    { title: '이름1', year: 1000 },
    { title: '이름2', year: 2000 },
    { title: '이름3', year: 3000 },
    { title: '이름4', year: 4000 },
  ];

  const renderData = data.map(adata => {
    return (
      <Data data={ adata } key={ adata.title } />
    );
  });

  return (
    <div className="App">
      { renderData }
    </div>
  );
}

export default App;

2. JS

import React from "react";

const Data = ({ data }) => { // props 였는데 { }로 원하는 부분만 빼오는게 가능
    return (
        <div className="data">
            <div className='data-title'>{data.title}</div>
            <div className='data-year'>{data.year}</div>
        </div>
    );
}

export default Data;

3. CSS

 
.data {
  display: flex;
  align-items: center;
  border: 1px solid gray;
  border-radius: 5px;
  padding: 10px;
  margin-bottom: 5px;
}

.data-title {
  flex-grow: 1;
}

.data-year {
  font-size: 10px;
  color: red;
}

 
 

map함수 사용할 때 자식에게 고유 키값을 줘야한다 

import './App.css';
import { useState, useEffect } from 'react';

function App() {
  const [state, setState] = useState(false);
  const toggle = () => setState(!state);
  useEffect(() => {
    console.log(state);
  }, [state])
  const renderState = state ? "1번!!" : "2번!!";

  return (
    <div className="App">
      <div>
        {renderState}
      </div>
      <button onClick={ toggle }>토글글</button>
    </div>
  );
}

export default App;

1.

import './App.css';
import { useState, useEffect } from 'react';
import Counter from './components/Counter'// 디렉토리 구조

function App() {

  const [name1, setName1] = useState("클릭!!");
  const clickBtn = () => {
    setName1("클릭변경함!");
  }
  return (
    <div className="App">

      <Counter click="클릭값"/>
      <Counter click={name1}/>
      <Counter />
      <br/>
      <br/>
      <button onClick={clickBtn}>두번째 버튼 변경!</button>
    </div>
  );
}

export default App;

2.

import React from "react";
import { useState, useEffect } from 'react';

const Counter = (props) => {
    const [num1, setNum1] = useState(0);

    const increment = () => {
      setNum1(num1+1);
    }

    const clickString = props.click || "받은 값 없음";
    return (
        <button onClick={increment}>전송버튼 ({clickString}) {num1}</button>
    );
}

export default Counter;

1번 코드에서 Counter 를 불러오고

click="클릭값" 으로 키="값" 형태로 보낸다

이것을 2번코드에서는 Counter 에서 props로 받아서 사용한다 

 

 

'코딩 공부 > REACT' 카테고리의 다른 글

[React] - 6차 - props 반복 학습  (0) 2022.02.18
[React] - 5차 - 조건 렌더링  (0) 2022.02.18
[React] - 3차 - useEffect 사용 예  (0) 2022.02.18
[React] - 2차 - useState 사용 예  (0) 2022.02.18
[React] - 1차  (0) 2022.02.18
 
import './App.css';
import { useState, useEffect } from 'react';

function App() {

  const [num1, setNum1] = useState(0);
  const [num2, setNum2] = useState(0);

  useEffect(() => {
    console.log("num1 변경: "+num1)
  }, [num1])
  useEffect(() => {
    console.log("num2 변경: "+num2)
  }, [num2])
  useEffect(() => {
    console.log("num1 OR num2 변경: "+num1,num2)
  }, [num1, num2])

  useEffect(() => {
    console.log("처음 한번만 실행")
  }, [])

  const incre1 = () => {
    setNum1(num1+1);
  }
  const incre2 = () => {
    setNum2(num2+1);
  }
  return (
    <div className="App">

      <button onClick={incre1}>전송 {num1}</button>
      <button onClick={incre2}>전송 {num2}</button>
    </div>
  );
}

export default App;

useEffect를 사용한 예인데 아직 완벽하게 이해하지 못해서

이것을 어떻게 활용하는지 좀더 공부 해봐야겠다

함수 뒤에 , [ ]안에 값이 변경되면 실행할 변수를 입력하면 조절이 가능하고 빈값으로 두면 최초 한번만 실행한다

'코딩 공부 > REACT' 카테고리의 다른 글

[React] - 6차 - props 반복 학습  (0) 2022.02.18
[React] - 5차 - 조건 렌더링  (0) 2022.02.18
[React] - 4차 -겹치는 코드 뭉쳐서 관리  (0) 2022.02.18
[React] - 2차 - useState 사용 예  (0) 2022.02.18
[React] - 1차  (0) 2022.02.18

+ Recent posts