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 ํ ํฐ์ด ์ด์ ์ ์ด๋ฏธ ์๋ฃ๋ ์์ฒญ์ ํ ํฐ์ด๋ผ๋ฉด ๊ทธ ํด๋น ์ธ์
์ ๋ชจ๋ ์ ์ง์ํค๋ ๋ฐฉ์์
๋๋ค.
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 ์ ๋ณด๋ฅผ ๋ณด๋ฉด ํด๋น ๋ฆฌ์คํฐ์ค๊ฐ ๋ด๊ธด ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
Name | Description | Type | Required |
---|---|---|---|
id | Unique ID for the provider | string | Yes |
name | Descriptive name for the provider | string | Yes |
type | Type of provider, in this caseย credentials | "credentials" | Yes |
credentials | The credentials to sign-in with | Object | Yes |
authorize | Callback to execute once user is to be authorized | (credentials, req) => Promise | Yes |
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 ํ ํฐ์ด ๊ฐ์ด ๋ค์ด์์ต๋๋ค.
...
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๋ก ์ค๋๋ค.
...
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
jwt: {
maxAge: 60 * 60
},
...
session
โjwtโ
์
๋๋ค. maxAge๋ก ๋ง๋ฃ์๊ฐ๋ฑ์ ์ค์ ํ ์ ์์ต๋๋ค.jwt
...
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
}
Frequently Asked Questions | NextAuth.js