๐Ÿ™ƒ Why Next-auth?

๊ฐœ๋ฐœ ๋ธ”๋กœ๊ทธยท2022๋…„ 12์›” 26์ผ
1

๋น„๋ฐ€๋ฒˆํ˜ธ ์—†๋Š” ๋กœ๊ทธ์ธ

Next.js๋Š” Serverless๋ฅผ ์ง€์›ํ•˜๋„๋ก ์ฒ˜์Œ๋ถ€ํ„ฐ ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  OAuth ์„œ๋น„์Šค์™€ ๋™๊ธฐํ™”ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ ๋ฐ์ดํ„ฐํ„ฐ๋ฒ ์ด์Šค ์œ ๋ฌด์— ๊ด€๊ณ„์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ MySQL, MongoDB, PostgreSQL ๋ฐ MariaDB์™€ ๊ฐ™์€ ์ธ๊ธฐ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ์ง€์›์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์„ธ์…˜ ์œ ์ง€๋ฅผ ํ•˜๊ฑฐ๋‚˜ JWT๋กœ ์ธ์ฆํ•˜๋Š” ๋ฐฉ๋ฒ• ๋‘˜๋‹ค ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์‰ฌ์šด ๊ฐœ๋ฐœ

20์ค„์ด๋ฉด ์ธ์ฆ๊ณผ์ •๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ React-hooks๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. SessionProvider ๋ฅผ ํ†ตํ•ด Context๋ฅผ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ณต์œ ํ• ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณด์•ˆ ์ œ๊ณต

๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์ €์žฅํ•˜์ง€ ์•Š๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋กœ๊ทธ์ธ๊ณผ ๋กœ๊ทธ์•„์›ƒ ์š”์ฒญ ์‹œ์—๋„ ๋‚ด๋ถ€์—์„œ CSRF ํ† ํฐ์„ ํ™œ์šฉํ•ด ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

  • CSRF ํ† ํฐ (Cross Site Request Forgery)
    ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ ์œ„์กฐ(CSRF)์˜ ์˜๋ฏธ

    CSRF๋ž€ ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ์— ํŠน์ • ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์„ธ์…˜ ์ฟ ํ‚ค๋‚˜ JWT๊ฐ€ ์ €์žฅ๋˜์–ด์žˆ์„ ๋•Œ ๊ณต๊ฒฉ์„ ๋‹นํ•ด ์ž์‹ ์˜ ์˜์‚ฌ์™€ ์ƒ๊ด€์—†์ด ๋„๋ฉ”์ธ์—์„œ ๊ณ„์ขŒ์ด์ฒด๋ฅผ ํ•˜๊ฑฐ๋‚˜ ๋ธ”๋กœ๊ทธ์— ๊ธ€์„ ์˜ฌ๋ฆฌ๊ฒŒ ๋˜๋Š” ๋“ฑ์˜ ๊ณต๊ฒฉ์„ ์ด์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค.
    CSRF ํ† ํฐ์€ ์ด๋ฅผ ์œ„ํ•ด ๊ตฌํ˜„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ž„์˜์˜ ๋‚œ์ˆ˜๊ฐ’์„ ์ฃผ๋Š”๋ฐ ํƒˆ์ทจ๋œ ์„ธ์…˜์ด๋”๋ผ๋„ CSRF ํ† ํฐ์ด ์ด์ „์— ์ด๋ฏธ ์™„๋ฃŒ๋œ ์š”์ฒญ์˜ ํ† ํฐ์ด๋ผ๋ฉด ๊ทธ ํ•ด๋‹น ์„ธ์…˜์„ ๋ชจ๋‘ ์ •์ง€์‹œํ‚ค๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๐Ÿ“•ย Configuration

1. Provider

CredentialsProvider({
	name: "Credentials",
	credentials: {
		username: { label: "Username", type: "text", placeholder: "jsmith" },
		password: { label: "Password", type: "password" },
	},
	async authorize(credentials, req) {
		const res = await fetch("/your/endpoint", {
			method: "POST",
			body: JSON.stringify(credentials),
			headers: { "Content-Type": "application/json" },
		});
		const user = await res.json();

		if (res.ok && user) {
			return user;
		}

		return null;
	},
});

OAuth, Email, Credentials Provider๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. OAuth, Email์˜ ๊ฒฝ์šฐ์—๋Š” ์ธ์ฆ ํ›„ ์น˜๋ค„์ง€๋Š” Response๋ฅผ ํ•ด๋‹น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ฃผ์ง€๋งŒ Credentials์˜ ๊ฒฝ์šฐ์—๋Š” ์ธ์ฆ ์„ฑ๊ณตํ›„ ๋ฆฌ์Šคํฐ์Šค๋ฅผ ํ•ด๋‹น ๋„๋ฉ”์ธ์—์„œ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ๊ตฌ๊ธ€๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” OAuth ์ธ์ฆ ํ›„ session ์ •๋ณด๋ฅผ ๋ณด๋ฉด ํ•ด๋‹น ๋ฆฌ์Šคํฐ์Šค๊ฐ€ ๋‹ด๊ธด ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ต์…˜

NameDescriptionTypeRequired
idUnique ID for the providerstringYes
nameDescriptive name for the providerstringYes
typeType of provider, in this caseย credentials"credentials"Yes
credentialsThe credentials to sign-in withObjectYes
authorizeCallback to execute once user is to be authorized(credentials, req) => PromiseYes
  • credentials

    export interface CredentialInput {
        label?: string;
        type?: string;
        value?: string;
        placeholder?: string;
    }
    
    ...
    credentials: {
      address: {}, // address: CredentialInput
      loginType: {}
    },
    ...

    Next-auth์—์„œ ์ธ์ฆ์„ ํ•˜๋Š” ์–‘์‹์„ ์ •ํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค. credentials์˜ ํ‚ค ๊ฐ’๋“ค๋กœ Next-auth๋Š” ๋กœ๊ทธ์ธ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
    credentials์˜ ํ‚ค๋“ค์ด CredentialInput ํƒ€์ž…์ธ ์ด์œ ๋Š” submit ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๋Š” input์˜ field๋ฅผ ๋ฏธ๋ฆฌ ํƒ€์ดํ•‘ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ธ๋ฐ์š”. ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค.

    signIn("credentials", {
    	address,
    	loginType: LOGIN_TYPES.KAIKAS,
    	callbackUrl: MAIN_PATH.PROJECT("klaytn"),
    	redirect: false,
    });

    ์ด์ œ next-auth์˜ ๋กœ๊ทธ์ธ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ํ•ด๋‹น ํ‚ค value๋“ค๊ณผ ์ถ”๊ฐ€ ์˜ต์…˜์„ ๋„ฃ์œผ๋ฉด ์ธ์ฆ์ด ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

  • authorize

    async authorize(credentials) {
      if (!credentials) return null
      const response = await setLogin(credentials?.address)
      return {
        id: new Date().getTime(),
        ...response,
        loginType: credentials?.loginType
      }
    }

    ์ด์ œ SignIn ๋ฉ”์†Œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์šฐ๋ฆฌ๋Š” session์— ์–ด๋–ค ์ •๋ณด๋ฅผ ๋‹ด์„์ง€ ์ฝœ๋ฐฑ์„ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•ด๋‹น ์ฝœ๋ฐฑ์—์„œ DB์˜ user ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์—์„œ access token์„ ๋ฐ›์•„์˜ค๋Š” ๋“ฑ์˜ ์กฐ์น˜๋ฅผ ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    [Object: null prototype] {
      address: '...',
      callbackUrl: '/',
      csrfToken: '...',
      json: 'true'
    }

    ์ธ์ž๊ฐ’์œผ๋กœ ๋“ค์–ด์˜ค๋Š” credentials์—๋Š” next-auth๊ฐ€ signIn์—์„œ ๋ฐ›์•„์˜จ ํ‚ค ๊ฐ’๋“ค๊ณผ ์ž์ฒด๋กœ ์ƒ์‚ฐํ•œ csrf ํ† ํฐ์ด ๊ฐ™์ด ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.

2. Secret & Debug

...
secret: process.env.AUTH_SECRET,
debug: process.env.NODE_ENV === 'development',
...
  • secret
    secret์— ๋“ค์–ด๊ฐ€๋Š” ์ž„์˜ ๋ฌธ์ž์—ด์€ ํ† ํฐ์„ ํ•ด์‹œํ•˜๊ณ  ์ฟ ํ‚ค๋ฅผ ์„œ๋ช…/์•”ํ˜ธํ™”ํ•˜๋ฉฐ ์•”ํ˜ธํ™” ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    ```ts
    const session = require('express-session');
    const cookieParser = require('cookie-parser');
    
    app.use(cookieParser(process.env.COOKIE_SECRET));
    app.use(session({
        saveUninitialized: false,
        resave: false,
        secret: process.env.COOKIE_SECRET
    }));
    ```
    
    express์—์„œ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์“ฐ์ด๋Š” ์ž„์˜ ๋ฌธ์ž์—ด๊ณผ ๋˜‘๊ฐ™์€ ์—ญํ• ์ž…๋‹ˆ๋‹ค.
  • debug
    ๋””๋ฒ„๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ true๋กœ ์ค๋‹ˆ๋‹ค.

3. session & jwt

...
session: {
	strategy: "database",
	maxAge: 30 * 24 * 60 * 60, // 30 days
},
jwt: {
  maxAge: 60 * 60
},
...
  • session
    ์‚ฌ์šฉ์ž์˜ ์„ธ์…˜์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ์ง€ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ถ”๊ฐ€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. strategy์˜ ๊ธฐ๋ณธ๊ฐ’์€ ์•”ํ˜ธํ™”๋œ JWT(JWE)๋ฅผ ์„ธ์…˜ ์ฟ ํ‚ค์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ธ โ€œjwtโ€์ž…๋‹ˆ๋‹ค. maxAge๋กœ ๋งŒ๋ฃŒ์‹œ๊ฐ„๋“ฑ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • jwt
    jwt์˜ ๊ฒฝ์šฐ session์˜ ์ „๋žต์„ default๋กœ ํ–ˆ์„ ์‹œ ํ† ํฐ์„ ์ธ์ฝ”๋”ฉํ•˜๋Š” ๊ณผ์ •์— ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด๋‚˜ ๋กœ์ง์„ ์ถ”๊ฐ€์ ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. maxAge์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์ด session์˜ maxAge๋กœ ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€ ๊ณ ๊ธ‰์˜ต์…˜์„ ์“ฐ์ง€ ์•Š์„ ๊ฒฝ์šฐ ์ด ์˜ต์…˜ ์ž์ฒด๋ฅผ ๋”ฐ๋กœ ์“ธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

4. callback

...
callbacks: {
	async session({ session, token }) {
    if (session && session.user) {
      session = {
        ...session,
        user: { ...session.user, ...token }
      }
      return session
    }
    return session
  },
  async jwt({ token, user, account }) {
    if (account && user) {
      return {
        ...token,
        ...user
      }
    }
    return token
  }
},
...
  • session callback
    ์‚ฌ์šฉ์ž๊ฐ€ useSession์ด๋‚˜ getSession ๋“ฑ ๊ธฐ์กด ์ €์žฅ๋œ ์„ธ์…˜์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋ ค ํ•  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. next-auth๊ฐ€ authorizeํ•œ ์ดํ›„๋กœ ๋ณด์•ˆ ๊ฐ•ํ™”๋ฅผ ์œ„ํ•ด ํ† ํฐ์˜ ํ•˜์œ„ ์ง‘ํ•ฉ๋งŒ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์„œ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ด๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    async session({ session, token, user }) {
    session.accessToken = token.accessToken
    session.user.id = token.id
    
            return session
        }
        ```
    
  • jwt callback
    ์ด ์ฝœ๋ฐฑ์€ JSON ์›น ํ† ํฐ์ด ์ƒ์„ฑ๋˜๊ฑฐ๋‚˜(์ฆ‰, ๋กœ๊ทธ์ธํ•  ๋•Œ) ์—…๋ฐ์ดํŠธ๋  ๋•Œ๋งˆ๋‹ค(์ฆ‰, ํด๋ผ์ด์–ธํŠธ์—์„œ ์„ธ์…˜์— ์•ก์„ธ์Šคํ•  ๋•Œ๋งˆ๋‹ค) ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.ย ๋ฐ˜ํ™˜๋œ ๊ฐ’์€ย ์•”ํ˜ธํ™”ย ๋˜์–ด ์ฟ ํ‚ค์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
    ์„ธ์…˜์ด ํ™œ์„ฑ์ƒํƒœ์ผ ๋•Œ๋งˆ๋‹ค ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์ด ์—ฐ์žฅ๋ฉ๋‹ˆ๋‹ค. โ‡’ ๊ธฐ์กด ๋กœ๊ทธ์•„์›ƒํ•˜์ง€ ์•Š๊ณ  ๋ธŒ๋ผ์šฐ์ € ๋‚˜๊ฐ”์„ ์‹œ ์ฟ ํ‚ค์— ์‹ฌ์–ด์ ธ์žˆ๋‹ค ๋‹ค์‹œ ๋“ค์–ด์™”์„ ๋•Œ ์„ธ์…˜์ด ํ™œ์„ฑํ™”๋˜์–ด ํ† ํฐ ์‹œ๊ฐ„์ด ์—ฐ์žฅ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    async jwt({ token, account, profile }) {
      if (account) {
        token.accessToken = account.access_token token.id = profile.id
        }
        return token
    }

๐Ÿžย Error case

Frequently Asked Questions | NextAuth.js

  • JSON ์›น ํ† ํฐ์—๋Š” ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์–‘์ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์ฟ ํ‚ค ์šฉ๋Ÿ‰์ด 4096byte์ด๊ธฐ ๋•Œ๋ฌธ์— authorizeํ•  ๋•Œ ์„ธ์…˜์— ๋„ˆ๋ฌด ๋งŽ์€ ์ •๋ณด๋ฅผ ๋‹ด์œผ๋ฉด ์ธ์ฆ์ž์ฒด๊ฐ€ ์•ˆ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž์˜ TIL

0๊ฐœ์˜ ๋Œ“๊ธ€