AuthContext๋ Context์ ์ผ๋ถ๋ก, ์ฌ์ฉ์ ์ธ์ฆ ์ํ์ ์ธ์
์ ๋ณด๋ฅผ ์ ๊ณตํฉ๋๋ค. better-auth๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค.
ํ์
์ ์
import type { Session, User } from "better-auth";
type AuthContext = {
/** ํ์ฌ ๋ก๊ทธ์ธํ ์ฌ์ฉ์ (null์ด๋ฉด ๋ฏธ์ธ์ฆ) */
user: User | null;
/** ํ์ฌ ์ธ์
์ ๋ณด (null์ด๋ฉด ๋ฏธ์ธ์ฆ) */
session: Session | null;
};
ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด์
๋๋ค. ๋ก๊ทธ์ธํ์ง ์์ ๊ฒฝ์ฐ null์
๋๋ค.
User ํ์
์ better-auth์์ ์ ๊ณตํ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์ ํ๋๋ฅผ ํฌํจํฉ๋๋ค:
type User = {
id: string;
name: string;
email: string;
emailVerified: boolean;
image: string | null;
createdAt: Date;
updatedAt: Date;
};
์ฌ์ฉ ์์:
import { Sonamu } from "sonamu";
class UserModelClass extends BaseModel {
@api({ httpMethod: "GET", guards: ["user"] })
async getMyProfile() {
const { user } = Sonamu.getContext();
if (!user) {
throw new UnauthorizedException("๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค");
}
return {
id: user.id,
email: user.email,
name: user.name,
};
}
}
session
ํ์ฌ ์ธ์
์ ๋ณด์
๋๋ค. ๋ก๊ทธ์ธํ์ง ์์ ๊ฒฝ์ฐ null์
๋๋ค.
Session ํ์
์ better-auth์์ ์ ๊ณตํ๋ฉฐ, ๋ค์ ํ๋๋ฅผ ํฌํจํฉ๋๋ค:
type Session = {
id: string;
userId: string;
token: string;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
};
์ฌ์ฉ ์์:
class SessionModelClass extends BaseModel {
@api({ httpMethod: "GET", guards: ["user"] })
async getSessionInfo() {
const { session } = Sonamu.getContext();
return {
sessionId: session.id,
expiresAt: session.expiresAt,
};
}
}
AuthContext๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด sonamu.config.ts์์ ์ธ์ฆ ์ค์ ์ ํด์ผ ํฉ๋๋ค.
๊ธฐ๋ณธ ์ค์
// sonamu.config.ts
import type { SonamuConfig } from "sonamu";
export default {
server: {
auth: {
emailAndPassword: { enabled: true },
},
},
} satisfies SonamuConfig;
์์
๋ก๊ทธ์ธ ์ถ๊ฐ
// sonamu.config.ts
export default {
server: {
auth: {
emailAndPassword: { enabled: true },
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
},
},
} satisfies SonamuConfig;
Guards๋ฅผ ํตํ ์ ๊ทผ ์ ์ด
API ๋ฉ์๋์ ์ธ์ฆ์ ์๊ตฌํ๋ ค๋ฉด guards ์ต์
์ ์ฌ์ฉํ ์ ์์ต๋๋ค:
class UserModelClass extends BaseModel {
@api({ httpMethod: "GET", guards: ["user"] })
async getMyData() {
const { user } = Sonamu.getContext();
// guards: ["user"]๋ก ์ธํด user๊ฐ null์ด ์๋์ด ๋ณด์ฅ๋จ
return this.findById("A", user!.id);
}
@api({ httpMethod: "GET", guards: ["admin"] })
async getAllUsers() {
// guards: ["admin"]์ผ๋ก ๊ด๋ฆฌ์๋ง ์ ๊ทผ ๊ฐ๋ฅ
return this.findMany("A", {});
}
}
Guard ์ฒ๋ฆฌ ๋ก์ง์ guardHandler์์ ๊ตฌํํฉ๋๋ค:
// sonamu.config.ts
import { Sonamu } from "sonamu";
export default {
server: {
apiConfig: {
guardHandler: (guard, request, api) => {
const { user } = Sonamu.getContext();
if (guard === "user" && !user) {
throw new UnauthorizedException("๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค");
}
if (guard === "admin") {
if (!user || (user as any).role !== "admin") {
throw new UnauthorizedException("๊ด๋ฆฌ์ ๊ถํ์ด ํ์ํฉ๋๋ค");
}
}
return true;
},
},
},
} satisfies SonamuConfig;
guardHandler๋ AsyncLocalStorage ๋ด๋ถ์์ ์คํ๋๋ฏ๋ก Sonamu.getContext()๋ฅผ ํตํด ํ์ฌ ์์ฒญ์ Context์ ์ ๊ทผํ ์ ์์ต๋๋ค.
User ํ์
ํ์ฅ
ํ๋ก์ ํธ์์ User ์ํฐํฐ์ ํ๋๋ฅผ ์ถ๊ฐํ ๊ฒฝ์ฐ, Sonamu UI๋ฅผ ํตํด ์ํฐํฐ๋ฅผ ์์ ํ๋ฉด ๋ฉ๋๋ค. ์ถ๊ฐํ ํ๋๋ user ๊ฐ์ฒด์์ ์ ๊ทผํ ์ ์์ง๋ง, TypeScript ํ์
์์ ์ฑ์ ์ํด ํ์
๋จ์ธ์ด ํ์ํฉ๋๋ค:
const { user } = Sonamu.getContext();
// role ํ๋๋ฅผ ์ถ๊ฐํ ๊ฒฝ์ฐ
const userRole = (user as any).role;
// ๋๋ ํ์
๊ฐ๋ ์ฌ์ฉ
interface ExtendedUser extends User {
role: "admin" | "user" | "manager";
}
function isExtendedUser(user: User | null): user is ExtendedUser {
return user !== null && "role" in user;
}
if (isExtendedUser(user)) {
console.log(user.role); // ํ์
์์
}
์ธ์ฆ ํ๋ฆ
- ํด๋ผ์ด์ธํธ๊ฐ
/api/auth/sign-in/email๋ก ๋ก๊ทธ์ธ ์์ฒญ
- better-auth๊ฐ ์ธ์ฆ ์ฒ๋ฆฌ ๋ฐ ์ธ์
์์ฑ
- ์ธ์
ํ ํฐ์ด ์ฟ ํค์ ์ ์ฅ๋จ
- ์ดํ ์์ฒญ์์ ์ฟ ํค์ ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์๋ณ
- API ๋ฉ์๋ ์คํ ์ Context์ user/session ์ฃผ์
Sonamu.getContext()๋ก ์ฌ์ฉ์ ์ ๋ณด ์ ๊ทผ
๊ด๋ จ ๋ฌธ์