본문 바로가기

코딩/NextJs

당근마켓 5 : authentication

728x90

인증- 유저가 누군지 알아내기
권한 -유저가 보려는 데이터에 접근 권한이 있는지

 

### Auth Logic
`phone # --> check exist --> it does, Send Token(connected with User) to the Phone (use Twilo) --> Client submit that Token --> If is correct,토큰에 연결된 UserID 세션에 저장 (Authentication)->인증 후, 사용된 토큰 삭제 (+ 인증된 유저와 연결된 모든 토큰 삭제)


type alias와 interface의 차이 
type-alias는 모든 타입을 선언할 때 사용될 수 있고, interface는 객체에 대한 타입을 선언할 때 사용될 수 있다. 둘 다 객체에 대한 타입을 선언하는 데 사용될 수 있는데, 확장 측면에서 사용 용도가 달라진다. 확장이 불가능한 타입을 선언하려면 type-alias를 사용하면 되고, 장이 가능한 타입을 선언하려면 선언 병합이 가능한 interface를 사용하면 된다.


Mutation 

Mutation은 서버에 데이터를 업데이트 하도록 서버에 네트워크 호출을 실시한다
CRUD(Create, Read, Update, Delete)에서
useQuery가 C를 맡았다면, useMutation은 RUD를 담당한다
Mutation은 변경 내용을 사용자에게 보여주거나 진행된 변경 내용을 등록하여 사용자가 볼 수 있게 하는 방법들이 있다

 

 

PlanetScale 데이터베이스 연결
pscale connect carrot-market
pscale connect carrot-market 실행하지 않으면 프리스마가 db에 접근하지 못함. 


문자열 쉽게 정수로 바꾸는법 

String -> Number : +"1234"
Number -> String : 1234 + ""


객체 내에서 if else기능을 사용하는 es6 문법

where:{

...(phone &&{phone:+phone})

...(phone &&{phone:+phone})

}

 

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}

model User {
  id        Int      @id @default(autoincrement())
  name      String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  avatar    String?
  phone     BigInt?     @unique // schema에서 8201040541140 이런식으로 입력하면 int가 아니여서 오류남 type                 
  email     String?  @unique         을 bigInt로 변경
 Token     Token[]   //생성됨
}

model Token {
  id        Int      @id @default(autoincrement())
  payload   String   @unique
  user      User     @relation(fields: [userId], references: [id]) //User 입력하면 위에서 참고하여 자동으로 입력됨
  userId    Int      //생성됨
 
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

userId가 생성된 이유는 model Token에 실제 user데이터가 db에 들어가진 않음 단순 텍스트이기 때문에 userId가 user임을 알려줌

 

schema생성 후 
npx prisma db push 
--> 새로운 토큰이 planetscale에 있는 케럿마켓 데이터베이스에 추가될 것임
db수정, prisma client새로만들어줌

 

planet scale db schema보면 userId는 존재하는 반면에 user 항목은 없다.

client 재생성 , 변경 후 

npm run dev 서버 재시작 

token에 undefiend인 프로퍼티를 읽을 수 없다고 에러가 난다면?

--> client를 재생성, 변경 후 서버를 재시작하지 않아서 기존 token 생성하기 전 구버전의 client를 사용하고 있는 거임

재시작해야 업뎃 됨

 

npx primsa studio에서 유저가 어떤 토큰을 갖고있고 

반대로 토큰이 어떤 유저를 갖고있는지 확인 할 수 있다.

async function handler(req: NextApiRequest, res: NextApiResponse) {
    const { phone, email } = req.body;
    const payload = phone ? { phone: +phone } : { email };

    const user = await clients.user.upsert({
        where: {
            ...payload,
        },
        create: {
            name: "Anonymous",
            ...payload,
        },
        update: {},
    });
    const token = await clients.token.create({ //create token and
        data: {
            payload: "1234",
            user: {
                connect: {
                    id: user.id,  //connect it to an existing User record via an ID field
                },
            },
        },
    });
    console.log(token);
    return res.status(200).end();
}
export default withHandler("POST", handler);

Update or create records (upsert)

upsert()는 기존 데이터를 업데이트하거나 새 데이터베이스 레코드를 생성합니다. 다음 쿼리는 upsert를 사용하여 특정 이메일 주소로 사용자 레코드를 업데이트하거나, 존재하지 않는 경우 해당 사용자 레코드를 생성합니다.
```
const upsertUser = await prisma.user.upsert({
where: {
email: 'hello@gmail.com', // email이 존재하는지 찾고, 
},
update: {
name: 'pizza',   //존재하면 update 실행
},
create: {
email: 'hello@gmail.com', // email이 존재하지 않다면 생성
name: 'pizza',
},
})
```
https://www.prisma.io/docs/concepts/components/prisma-client/crud#update-or-create-records
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#upsert

 

connect

connect 쿼리는 ID 또는 고유 식별자를 지정하여 레코드를 기존 relation 레코드에 연결합니다. (레코드를 연결)

@relation 관계가 있을 경우에
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#connect

 

Payload

페이로드(payload) 전송되는 데이터를 의미합니다. 데이터를 전송할 때, 헤더와 메타데이터, 에러 체크 비트 등과 같은 다양한 요소들을 함께 보내어, 데이터 전송의 효율과 안정성을 높히게 됩니다. 이 때, 보내고자 하는 데이터 자체를 의미하는 것이 바로 페이로드입니다. 우리가 택배 배송을 보내고 받을 때, 택배 물건이 페이로드이고, 송장이나 박스, 뾱뾱이와 같은 완충재 등등은 부가적인 것이기 때문에 페이로드가 아닙니다.

 

json으로 보는 실제 데이터에서의 payload는 json에서 “data”입니다. 그 이외의 데이터들은 전부 통신을 하는데 있어 용이하게 해주는 부차적인 정보들입니다.

async function handler(req: NextApiRequest, res: NextApiResponse) {
    const { phone, email } = req.body;
    const payload = phone ? { phone: +phone } : { email };


    const token = await clients.token.create({
        data: {
            payload: "1234",
            user: {
                connectOrCreate:{
                    where: {
                        ...payload,
                    },
                    create: {
                        name: "Anonymous",
                        ...payload,
                    },
                }
            },
        },
    });
    console.log(token);
    return res.status(200).end();
}
export default withHandler("POST", handler);

 

 

 


connectOrCreate 프리즈마의 강력한도구 !

조건을 만족하는 유저가있으면 connect하고 

없으면 생성하기


connectOrCreate
connectOrCreate는 ID 또는 고유 식별자로 기존 관련 레코드에 레코드를 연결하거나 레코드가 존재하지 않는 경우 새 관련 레코드를 생성합니다.

 

아래 글의 unique violation 참조
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#connectorcreate

 

Prisma Client API (Reference)

API reference documentation for Prisma Client.

www.prisma.io

 

That is how we can have `passwordless` log in. 로그인 할 때 마다 토큰을 받는 방식구현하기

Twilio  통해 메세지 보내기

Twilio
Twilio는 전화 걸기 및 받기, 문자 메시지 보내기 및 받기, 웹 서비스 API를 사용하여 기타 커뮤니케이션 기능 수행을 위한 프로그래밍 가능한 커뮤니케이션 도구를 제공합니다.
https://www.twilio.com/

Twilo Document
https://www.twilio.com/docs

Messaging Services
메시징 서비스는 특정 사용 사례 및 메시징 캠페인을 위한 메시징 기능을 번들로 제공하는 컨테이너입니다. 특정 채널 및 번호 유형 중에서 선택하여 발신자를 메시징 서비스와 일치시키십시오.

랜덤 6자리 숫자 생성

방법 1) Math.floor(100000 + Math.random() * 900000) + "";  
방법 2) String(Math.random()).substring(2,8)

 

+Math.random으로 최솟값 최댓값만들기 

Math.floor(Math.random() * (최대값 - 최소값) + 최소값)

Math.random(): 0~1 사이의 부동소수점 값 (1은 포함하지 않습니다) 0~0.999

 0 <= random <= 9 

Math.floor(Math.random() * 10);

Math.random() 함수의 결과는 0~0.99999...이고,

Math.random() * 10 의 결과는 0~9.99999...입니다.

따라서, Math.floor(Math.random() * 10)의 결과는 0~9 범위의 정수입니다.

Math.random() 함수를 이용하여 정수 범위의 난수를 생성할 때는 

이렇게 Math.random() 함수를 호출한 결과를 조작하고, Math.floor()를 사용합니다.

 

 0 <= random <= 10 

Math.floor(Math.random() * 11);

10을 포함하는 정수 난수를 얻고 싶다면

Math.random() 함수의 결과에 11을 곱하고, 소수점 이하를 버림합니다.

 0 <= random <= 99 

Math.floor(Math.random() * 100);

0~99 범위의 정수 난수를 생성하려면

Math.random() 함수의 결과에 100을 곱해주고, 소수점 이하를 버림합니다.

 

 0 <= random <= 100 

Math.floor(Math.random() * 101);

0~100 범위의 정수 난수를 생성하려면

>Math.random() 함수의 결과에 101을 곱하고, 소수점 이하를 버림합니다.

 (7) 1 <= random <= 10 

Math.random() * 10) + 1;

최소값을 지정하고 싶을 때는 

Math.random() * (max - min + 1) 값을 계산하고, 소수점 이하를 버림합니다.

그리고, min 값을 더해줍니다.

1~10 범위의 정수 난수를 계산하기 위해서 아래와 같이 계산하였습니다.

Math.floor(Math.random() * (10 -1 + 1)) + 1

= Math.floor(Math.random() * 10) + 1

 

 (8) 2 <= random <= 5 

Math.floor(Math.random() * 4) + 2;

(7)번 예제와 같이 범위를 지정하는 예제입니다.

Math.floor(Math.random() * (5 - 2 + 1)) + 2

= Math.floor(Math.random() * 4) + 2

(Math.random()*(최대-최소))+최소

예)

출처:https://hianna.tistory.com/454

출처&nbsp;https://fromnowwon.tistory.com/entry/math-random

import twilio from "twilio";
const twiloClient = twilio(process.env.TWILIO_SID, process.env.TWILIO_TOEKN);
async function handler(
    req: NextApiRequest,
    res: NextApiResponse<ResponseType>
) {
    const { phone, email } = req.body;
    const user = phone ? { phone: +phone } : email ? { email } : null;/ /유저 프로필 정보를 담는 변수를 payload라는 이름으로 사용했었는데 이 변수는 token을 만들기위해 사요하니까 user로 변경
    if (!user) return res.status(400).json({ ok: false });
    const payload = Math.floor(100000 + Math.random() * 900000) + "";
    const token = await clients.token.create({
        data: {
            payload,
            user: {
                connectOrCreate: {
                    where: {
                        ...user,
                    },
                    create: {
                        name: "Anonymous",
                        ...user,
                    },
                },
            },
        },
    });
    if (phone) {
        const message = await twiloClient.messages.create({
            messagingServiceSid: process.env.MESSAGING_SERVICE_SID,
            to: process.env.MY_PHONE,  // 원래는 to: req.body.phone이여함 기본버전이여서 어쩔 수 없이 지정함 
            body: `your login token is ${payload}`,
        });
        console.log(message);
    }
    return res.json({
        ok: true,
    });
}
export default withHandler("POST", handler);

 

twilio 가입 후 

sid, token,messaging services sid,myphone(국가번호도 기재해야함)
TWILIO_SID=
TWILIO_TOKEN=
TWILIO_MSID=
MY_PHONE=+821012345678
.env에 저장
서버재시작하지 않으면 .env에 입력한 환경변수 읽지못함

npm i twilio

input에 입력한 phone이 들어가야하지만 
제한된 계정이기 때문에 무슨 번호를 입력해도 내폰으로 문자옴


Node.js용 프로그래밍 가능한 SMS 빠른 시작
Twilio의 메시징 채널을 사용하여 전송된 모든 메시징은 A2P(Application-to-Person) 메시징으로 취급되며 Twilio의 메시징 정책이 적용됩니다. 몇 줄의 코드로 Node.js 애플리케이션에서 Twilio Programmable SMS로 문자 메시지를 보내고 받을 수 있습니다.
```
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const twilioClient = require('twilio')(accountSid, authToken);

const messageInstance: MessageInstance = await twilioClient.messages.create({
messagingServiceSid: process.env.MESSAGING_SERVICES_SID,
to: process.env.MY_PHONE_NUMBER as string,
body: `휴대폰 로그인을 위한 토큰 ${createdRandomPayload} 입니다.`,
});
```
https://www.twilio.com/docs/sms/quickstart/node



nodeMailer로 메일 보내기

SendGrid가 너무 오래걸릴 것 같아 테스트 용으로 nodeMailer를 사용했습니다. 네이버 아이디만 있으면 사용 가능합니다.

1. 네이버 이메일 -> 내 메일함 오른쪽 설정버튼 클릭 -> 상단 메뉴들 중 'POP/IMAP 설정' -> 바로 아래에 'IMAP/SMTP 설정' -> 사용함, 동기화 메일 제한은 1000으로 해놓았습니다.

2. 하단에 보시면 SMTP 서버 명, SMTP 포트가 표시되어있습니다.

3. server폴더 내에 email.ts를 만들어줍니다.

4. npm install --save nodemailer @types/nodemailer

5. email.ts에
-------------------------------------------------------------
import nodemailer from "nodemailer";

const smtpTransport = nodemailer.createTransport({
service: "Naver",
host: "smtp.naver.com",
port: 587,
auth: {
user: process.env.MAIL_ID,
pass: process.env.MAIL_PASSWORD,
},
tls: {
rejectUnauthorized: false,
},
});

export default smtpTransport;
---------------------------------------------------------

6. enter.tsx에 돌아오셔서
------------------------------------------------------------------
if (email) {
const mailOptions = {
from: process.env.MAIL_ID,
to: email,
subject: "Nomad Carrot Authentication Email",
text: `Authentication Code : ${payload}`,
};
const result = await smtpTransport.sendMail(
mailOptions,
(error, responses) => {
if (error) {
console.log(error);
return null;
} else {
console.log(responses);
return null;
}
}
);
smtpTransport.close();
console.log(result);
}
--------------------------------------------------------
여기서 메일 옵션의 from은 제 아이디를 써야 동작하는 것 같더라구요...
carrot@noreply.com 같은 존재하지 않는 이름으로 해보았는데 오류가 발생해서
테스트 용으로 사용할 거니까 일단 제 메일주소로 진행했더니 정상적으로 보내집니다.

혹시 저처럼 sendgrid에서 메일 오는 것 기다리기 지치신 분들은 sendgrid사용 전에 이렇게 해서 테스트해보시는것도 추천드립니다...

혹시 글이 부족하다면 'nodemailer 네이버 연동'으로 검색하시면 어렵지 않게 사용법 확인 가능합니다!
 

 

❗ npx prisma studio에서 에서 유저를 삭제하면 오류가난다 
token  schema를 살펴보면  user가 반드시 있어야하는데 상응하는 user가 사라지면 안 되어서 오류가 남 
onDelete:cascade 부모 user가 사라지면 자식인 token도 사라지는것 
npx prsima db push
bigInt , onDelete  변경한 부분을  데이터베이스 update 도와줌  


res.end()는 보내줄 아무 데이터도 없는데 response를 끝내고 싶을 때 사용한다고 하네요

 

 const onTokenValid = (validForm: TokenForm) => {

        console.log(validForm);//Object  token: "12345"
        confirmToken(validForm); //confirmToken : useMutate함수 !  validForm을 인자로 넣어서 api호출 함수 작동!
        
    };

 

 

세션 vs 토큰 vs 쿠키? 기초개념 잡아드림. 10분 순삭! (노마드코더 유튜브)
 https://www.youtube.com/watch?v=tosLBcAX1vk 

 

 

 

쿠키:

session ID를 전달하기 위한 매개체 

session id뿐만아니라 다른 정보도 포함할 수 잇음

유저 정보 뿐만아니라 사이트의 kor버전 설정

sever에 req 요청 시 마다 쿠키 전달

서버가 브라우저에 쿠키 저장 후 해당 사이트 방문시마다 브라우저는 해당 쿠키 요청과 함께 보냄

only browser! no native app

 

sessionID

req 요청 할 때마다 session DB와 비교확인함 

로그인 된 유저의 모든 정보 저장  --> 인스타그램의 여러 계정 중 특정 계정 로그아웃시키기 , 넷플릭스 여러 계정 중 현재 동시 접속 유저 몇명 인지 등을 구현 하는것 모두 db가 있어야함

로그인유무

 

token 

서버가 기억하는 매우 이상한 텍스트 e.g) id card 같은 것 

 

JWT 

DB확인 하는게 아니고 정보확인을하고 SIGN해서 전달 

TOKEN이 유효한가만 확인함

구현이 쉬움 

e.g )코로나 qr 인증 

강제로 로그아웃하거나 이런 기능은 안됨

유저에대해 관리할게 많거나 큰 플랫폼이면 사용하는데 제약이있음

파일을 누구나 볼 수 있음 암호화 필요

제약되는 는 점을 확실히 알아둬야함. 

 

https://medium.com/@alysachan830/cookie-and-session-i-the-basic-concept-fea3d52bc8d3


iron session 

# iron session : https://github.com/vvo/iron-session
- 데이터를 저장하기 위해 서명, 암호화된 쿠키를 사용하는 nodejs stateless 세션 도구
- JWT는 암호화되지 않고 서명이 되어있음
- 유저가 안에 있는 정보를 볼 수 없음
- 세션을 위한 백엔드 구축이 필요 없음

Next.js, Express, Nest.js, Fastify 및 모든 Node.js HTTP 프레임워크와 함께 작동합니다. 세션 데이터는 암호화된 쿠키("seals")에 저장됩니다. 그리고 당신의 서버만이 세션 데이터를 디코딩(decode)할 수 있습니다. 세션 ID가 없으므로 서버 관점에서 iron session을 "stateless"로 만듭니다.


npm i iron-session
```
import { withIronSessionApiRoute } from "iron-session/next";

export default withIronSessionApiRoute(NextApiHandler)
```

https://github.com/vvo/iron-session

 

GitHub - vvo/iron-session: 🛠 Next.js stateless session utility using signed and encrypted cookies to store data. Also works w

🛠 Next.js stateless session utility using signed and encrypted cookies to store data. Also works with Express, and Node.js HTTP servers - GitHub - vvo/iron-session: 🛠 Next.js stateless session util...

github.com

 

사용법 위 링크 참조 !



withIronSessionApiRoute도우미 함수로 객체로 감싸면 iron session이 알아서 요청 객체 안에 요청하는 세션 유저인req.session.user 를 담아줌  !옵션으로 쿠키이름, 패스워드 같이보내야함

export default withIronSessionApiRoute(withHandler("POST", handler), {
    cookieName: "carrotsession",
    password: "fkdjiwkfjisldkkflfijkd15321535132d1fd1w35e1f",
});


핸들러를 도우미 함수로 감쌌기때문에 handler 함수 내에서 console.log(req.session)하면  확인 할 수있음
특정 토큰을 가진 유저를 찾고 req.session.user에 넣고 req.session.save 할꺼임  req.session.안에 이름은 우리가지은거임
로그인하고 토큰 후 개발자도구에서 cookie확인

  const { token } = req.body;
    const exists = await clients.token.findUnique({   //특정 토큰을 가진 유저를 찾고
        //prisma
        where: {
            payload: token,
        },
        /*    include: { user: true }, */ //dosen't neeed now
    });
    if (!exists) res.status(404).end();
    req.session.user = {                                          //req.session.user에 넣고
        id: exists?.userId,
    };
    await req.session.save();                                // req.session.save() 세션 데이터를 암호화하고 쿠키를 설정
    console.log(exists);
    res.status(200).end();
}

 




브라우저가 받은 쿠키가 유용한지 테스트 해볼거임.

get user profile하기 !

우선 그 전에

ㅇ타입스크립트는 req.session에 user가 있다는걸 모름 user가 어떤 내용을 가지고 있는지 알려줘야함


declare module "iron-session" { //typescript로  user가 어떤 내용을 가지는지 정의해줘야함 타입스크립트는 req.session
    interface IronSessionData {    에 유저가 있다는걸 모름 
        user?: {
            id: number;
        };
    }
}
async function handler(
    req: NextApiRequest,
    res: NextApiResponse<ResponseType>
) {
    const { token } = req.body;
    const exists = await clients.token.findUnique({
        //prisma
        where: {
            payload: token,
        },
        /*    include: { user: true }, */ //dosen't neeed now
    });
    if (!exists) return res.status(404).end(); //return 안 쓰면  typescript가 여기서 끝나는지모르고 밑에까지 이어지는줄 암.
    req.session.user = {  //여기에 에러가났었음
        id: exists?.userId,
    };
    await req.session.save();
    console.log(exists);
    res.status(200).end();
}
export default withIronSessionApiRoute(withHandler("POST", handler), {
    cookieName: "carrotsession",
    password: "fkdjiwkfjisldkkflfijkd15321535132d1fd1w35e1f",
});

https://github.com/vvo/iron-session#typing-session-data-with-typescript
api > user > me.tsx 생성후 

위에 템플릿 복사 후에 필요없는걸 지우고 맨 아래 withHandler("GET")형태로 변형 후 

console.log(req.session.user)하여 쿠키가 갖고있는 세션에 userId가 저장되어 있어 user정보를 가져올 수 있음

쿠키 특성상 새로고침할 때마다 서버에 요청하여 콘솔에 여러개의 console찍히는 것도 확인 !


모든 api url은 실제 백엔드 없이 개별적으로 동작하기 때문에
각 api 마다 type과 withIronSessionApiRoute config를 매번 설정해줘야함

 

profile 가져오기 

저장된 쿠키 이용해서 가져오기

    const profile = await clients.user.findUnique({
        where: { id: req.session.user?.id },
    });
    res.json({
        ok: true,
        profile,
    });
}

 

 



32자 랜덤 비밀번호 필요하신 분들은 아래 사이트에서 Length 32로 설정 후 복사해서 사용하시면 됩니다.
https://1password.com/password-generator/

 

 

새 api만들 때마다 작성 할 수 없으니 

wrapper를 만들자 

모든 API 경로 파일 또는 페이지에서 비밀번호와 쿠키 이름을 전달하지 않으려면 다음과 같이 래퍼를 만들 수 있습니다.
1withHandler 안에 넣기 -->지금 더 넣으면 복잡
2iron session의 wrapper만들기 

create server > withSession.ts
1.api route에서 session을 받아오기 위한 function
2페이지를 rendering할 대 next.js의 serversiderendering에서 session

wrapping 하기 전
간단하게 작성 !
.env에서 오는 pasword가 undfined일 수도 있다는 typescript error --> password 뒤에 !를 작성해준다.

다시 돌아와서

confirm에서 확인 끝난 후에 

res.json({ok:true} 작동 후 
fe에서 api로부터 ok:tre을 받았다는것 다른 페이지로 전환해야한다는 뜻!

    const router = useRouter();
    useEffect(()=>{
        if(tokenData?.ok){
            router.push("/")
        }
    },[tokenData])

 

유저 인증 후에는 token지우기 ! 

인증 할 때마다 새로운 token을 갖는데 지우지 않으면 db에 로그인 할 때마다 token 쌓임

confirm.tsx

    await clients.token.deleteMany({
        where: {
            userId: foundToken.userId, //exists이름 구려서 foundToken이라고 변경함
        },
    });

쉽게 authentication하는 법

 

NextAuth.js
Next.js에서 Authentication 구현을 도와주는 패키지이다. 입맛대로 바꾸기 쉽지 않다. 데이터베이스가 없어서 유저는 로그인 시킬 수 있지만 어떤 유저인지 알지 못함. prisma db 이용 할꺼면 따로 확인 해야 할 문서가 있다.
npm i next-auth

Getting Started
아래 예제 코드는 Next.js 앱에 인증을 추가하는 방법을 설명합니다.
```
// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"

export default NextAuth({
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
})
```
https://next-auth.js.org/getting-started/example

Next.js에 대한 인증
NextAuth.js는 Next.js 애플리케이션을 위한 완전한 오픈 소스 인증 솔루션입니다. Next.js 및 Serverless를 지원하도록 처음부터 설계되었습니다.
https://www.npmjs.com/package/next-auth

'코딩 > NextJs' 카테고리의 다른 글

당근마켓 :6 Authorization  (0) 2023.04.11
당근마켓 4 :Refactoring  (0) 2023.03.30
당근마켓 3: database setup  (0) 2023.03.29
당근마켓 2:Tailwind css  (0) 2023.03.17
당근마켓 :1 NextJs, Tailwind setup  (0) 2023.03.15