๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
AuthContext๋Š” Context์˜ ์ผ๋ถ€๋กœ, ์‚ฌ์šฉ์ž ์ธ์ฆ ์ƒํƒœ์™€ ์„ธ์…˜ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. better-auth๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

ํƒ€์ž… ์ •์˜

import type { Session, User } from "better-auth";

type AuthContext = {
  /** ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž (null์ด๋ฉด ๋ฏธ์ธ์ฆ) */
  user: User | null;
  /** ํ˜„์žฌ ์„ธ์…˜ ์ •๋ณด (null์ด๋ฉด ๋ฏธ์ธ์ฆ) */
  session: Session | null;
};

์†์„ฑ

user

user: User | 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

session: Session | null
ํ˜„์žฌ ์„ธ์…˜ ์ •๋ณด์ž…๋‹ˆ๋‹ค. ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ 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); // ํƒ€์ž… ์•ˆ์ „
}

์ธ์ฆ ํ๋ฆ„

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ /api/auth/sign-in/email๋กœ ๋กœ๊ทธ์ธ ์š”์ฒญ
  2. better-auth๊ฐ€ ์ธ์ฆ ์ฒ˜๋ฆฌ ๋ฐ ์„ธ์…˜ ์ƒ์„ฑ
  3. ์„ธ์…˜ ํ† ํฐ์ด ์ฟ ํ‚ค์— ์ €์žฅ๋จ
  4. ์ดํ›„ ์š”์ฒญ์—์„œ ์ฟ ํ‚ค์˜ ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„
  5. API ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „ Context์— user/session ์ฃผ์ž…
  6. Sonamu.getContext()๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์ ‘๊ทผ

๊ด€๋ จ ๋ฌธ์„œ