EP15. Supabase Auth Middleware
์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ ์ ํ๋ฆฌ์ผ์ด์
์์ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ ์ ์ฌ์ฉ์๋ ํด๋ผ์ด์ธํธ์ ์ ์์ ํ์ธํ๋ ์ญํ ์ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๊ธฐ ์ํ ๊ถํ์ด ์๋์ง๋ฅผ ๊ฒ์ฆํ๊ณ , ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์ฐจ๋จํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ ํฐ์ด๋ ์ธ์
์ ์ฌ์ฉํ์ฌ ์ธ์ฆ์ ์ํํฉ๋๋ค.
1. ์ธ์ฆ ๋ฏธ๋ค์จ์ด์ ํ์์ฑ
AOP(Aspect-Oriented Programming)๋ ํ๋ก๊ทธ๋จ์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๊ณตํต์ ์ธ ๊ด์ฌ์ฌ๋ฅผ ๋ถ๋ฆฌํ์ฌ ๋ชจ๋ํํ๋ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์
๋๋ค. ์ฃผ์ ๋ชฉ์ ์ ๋ก๊น
, ๋ณด์, ํธ๋์ญ์
๊ด๋ฆฌ ๊ฐ์ ํก๋จ ๊ด์ฌ์ฌ๋ฅผ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์์ ๋ถ๋ฆฌํ์ฌ ์ฝ๋์ ์ฌ์ฌ์ฉ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ํฅ์์ํค๋ ๊ฒ์
๋๋ค.
2. ์ ์ ํด๋์ค ๊ตฌํ
supabase/api ๋๋ ํ ๋ฆฌ ํ์์ entities๋๋ ํ ๋ฆฌ๋ฅผ ์ถ๊ฐํ๊ณ user.ts ๊ตฌํ์ฒด ํ์ผ๋ ์ถ๊ฐํฉ๋๋ค.
import { User as SupabaseUser } from "https://esm.sh/@supabase/supabase-js@2";
export class ServiceUser {
// ServiceUser ํด๋์ค๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ด๋ ํด๋์ค์
๋๋ค.
// SupabaseUser ํด๋์ค๋ฅผ ์ด์ฉํด ์์ฑํฉ๋๋ค.
// ํ์ํ ์ ๋ณด๊ฐ ์๋ค๋ฉด ์ถ๊ฐํ ์ ์์ต๋๋ค.
id: string;
email: string | null;
createdAt: string;
constructor(id: string, email: string | null, createdAt: string) {
this.id = id;
this.email = email;
this.createdAt = createdAt;
}
static fromUser(user: SupabaseUser): ServiceUser {
return new ServiceUser(
user.id,
user.email ?? null,
user.created_at,
);
}
}
TypeScript
๋ณต์ฌ
3. ์ธ์ฆ ๋ฏธ๋ค์จ์ด ๊ตฌํ
supabase/api ๋๋ ํ ๋ฆฌ ํ์์ middleware๋๋ ํ ๋ฆฌ๋ฅผ ์ถ๊ฐํ๊ณ authMiddleware.ts ๊ตฌํ์ฒด ํ์ผ๋ ์ถ๊ฐํฉ๋๋ค.
import { ServiceUser } from "../entities/user.ts";
import { supabase } from "./../lib/supabase.ts";
import { Context, Next } from "https://deno.land/x/hono/mod.ts";
// ์ธ์ฆ ๋ฏธ๋ค์จ์ด ํจ์ ์ ์
export async function authMiddleware(c: Context, next: Next) {
// ํด๋ผ์ด์ธํธ๋ก๋ถํฐ Authorization ํค๋๋ฅผ ๋ฐ์์ต๋๋ค.
const authHeader = c.req.header("Authorization");
// Authorization ํค๋๊ฐ ์๊ฑฐ๋ Bearer๋ก ์์ํ์ง ์์ผ๋ฉด 401 Unauthorized ์๋ต์ ๋ณด๋
๋๋ค.
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return c.json({ code: 401, message: "Unauthorized" }, 401);
}
// Authorization ํค๋์์ JWT ํ ํฐ์ ์ถ์ถํฉ๋๋ค.
const jwt = authHeader.split(" ")[1];
// JWT ํ ํฐ์ ๊ฒ์ฆํ๊ณ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
const serviceUser = await decodeJWT(jwt);
// JWT ํ ํฐ ๊ฒ์ฆ์ ์คํจํ๊ฑฐ๋ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ผ๋ฉด 401 Unauthorized ์๋ต์ ๋ณด๋
๋๋ค.
if (!serviceUser) {
return c.json({ code: 401, message: "Unauthorized" }, 401);
}
// ๊ฒ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ปจํ
์คํธ์ ์ ์ฅํฉ๋๋ค.
c.set("user", serviceUser);
// ๋ค์ ๋ฏธ๋ค์จ์ด ๋๋ ํธ๋ค๋ฌ๋ฅผ ํธ์ถํฉ๋๋ค.
await next();
}
// JWT ํ ํฐ์ ๊ฒ์ฆํ๊ณ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ํจ์ ์ ์
async function decodeJWT(jwt: string): Promise<ServiceUser | null> {
// Supabase๋ฅผ ์ฌ์ฉํ์ฌ JWT ํ ํฐ์ ๊ฒ์ฆํ๊ณ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
const { data, error } = await supabase.auth.getUser(jwt);
// JWT ๊ฒ์ฆ์ ์คํจํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ก๊ทธ์ ์ถ๋ ฅํ๊ณ null์ ๋ฐํํฉ๋๋ค.
if (error) {
console.error("JWT verification failed:", error.message);
return null;
}
// ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์์ผ๋ฉด ๋ก๊ทธ์ ์ถ๋ ฅํ๊ณ null์ ๋ฐํํฉ๋๋ค.
if (data.user == null) {
console.error("User not exist");
return null;
}
// ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ServiceUser ๊ฐ์ฒด๋ก ๋ณํํ์ฌ ๋ฐํํฉ๋๋ค.
return ServiceUser.fromUser(data.user);
}
TypeScript
๋ณต์ฌ
4. ์ธ์ฆ ๋ฏธ๋ค์จ์ด ์ ์ฉ
๋ฏธ๋ค์จ์ด๋ Router ๋ ์ด์ด์์ ์ ์ฉํฉ๋๋ค.
supabase/api/routers/userRouter.ts
import { authMiddleware } from "../middleware/authMiddleware.ts";
import { UserController } from "./../controllers/userController.ts";
import { Hono } from "https://deno.land/x/hono/mod.ts";
const userRouter = new Hono();
const userController = new UserController();
// ์ฌ์ฉ์ ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํฉ๋๋ค. ๐๐๐๐๐๐
userRouter.use(authMiddleware); // ๐๐๐๐๐๐
userRouter.post(
"/v1/fcmToken",
(c) => userController.postFCMTokenV1(c),
);
userRouter.get(
"/v1/fcmToken",
(c) => userController.getFCMTokensV1(c),
);
userRouter.delete(
"/v1/fcmToken",
(c) => userController.deleteFCMTokenV1(c),
);
export default userRouter;
TypeScript
๋ณต์ฌ
โข
"/v1/fcmToken"์ POST, GET, DELETE ์์ฒญ์ ํ ๋, Request Header์ ์ ํจํ ์ธ์ฆ ์ ๋ณด๊ฐ ์์ผ๋ฉด ์ฐ์ ์ ์ผ๋ก 401 ์ค๋ฅ ์ฝ๋๋ฅผ ๋ฐํํ๊ฒ ๋ฉ๋๋ค.
5. ์ปจํธ๋กค๋ฌ ์์ญ์์์ ์ฌ์ฉ
Router์์ ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๋๋ก ์ ์ธํ๋ฉด, ์ธ์ฆ ๋ฏธ๋ค์จ์ด๊ฐ ์ฌ์ฉ์์ ์ธ์ฆ์ ๊ฒ์ฌํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ปจํธ๋กค๋ฌ์๊ฒ ์ ๋ฌํ๊ฒ ๋ฉ๋๋ค.
์ปจํธ๋กค๋ฌ์์๋ ์ธ์ฆ ๋ฏธ๋ค์จ์ด์์ ๋ด์์ค ์ ์ ์ ๋ณด๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค.
supabase/functions/api/controllers/userController.ts
--- a/supabase/functions/api/controllers/userController.ts
+++ b/supabase/functions/api/controllers/userController.ts
@@ -2,6 +2,7 @@ import { ResponseCode } from "./../lib/response/responseCode.ts";
import { Context } from "https://deno.land/x/hono/mod.ts";
import { UserService } from "../services/userService.ts";
import { createResponse } from "../lib/response/responseFormat.ts";
+import { ServiceUser } from "../entities/user.ts";
// UserController ํด๋์ค๋ FCM ํ ํฐ์ ๊ด๋ฆฌํ๊ธฐ ์ํ ์ปจํธ๋กค๋ฌ์
๋๋ค.
export class UserController {
@@ -14,6 +15,8 @@ export class UserController {
// postFCMTokenV1 ๋ฉ์๋๋ FCM ํ ํฐ์ ์ถ๊ฐํ๋ API ์๋ํฌ์ธํธ์
๋๋ค.
async postFCMTokenV1(c: Context) {
+ // ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ๋์ด์จ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
+ const requestUser = c.get("user") as ServiceUser;
// ์์ฒญ์์ fcmToken์ ์ถ์ถํฉ๋๋ค.
const { fcmToken } = await c.req.json();
@@ -29,7 +32,7 @@ export class UserController {
}
// UserService๋ฅผ ํตํด FCM ํ ํฐ์ ์ถ๊ฐํฉ๋๋ค.
- const result = await this.userService.addFCMToken("mock_user_id", fcmToken);
+ const result = await this.userService.addFCMToken(requestUser.id, fcmToken);
// ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ ์ ํ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
return c.json(
@@ -49,8 +52,10 @@ export class UserController {
// getFCMTokensV1 ๋ฉ์๋๋ ํน์ ์ฌ์ฉ์์ FCM ํ ํฐ๋ค์ ๊ฐ์ ธ์ค๋ API ์๋ํฌ์ธํธ์
๋๋ค.
async getFCMTokensV1(c: Context) {
+ // ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ๋์ด์จ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
+ const requestUser = c.get("user") as ServiceUser;
// UserService๋ฅผ ํตํด ์ฌ์ฉ์ ID์ ํด๋นํ๋ FCM ํ ํฐ๋ค์ ๊ฐ์ ธ์ต๋๋ค.
- const tokens = await this.userService.getFCMTokensByUserID("mock_user_id");
+ const tokens = await this.userService.getFCMTokensByUserID(requestUser.id);
// ํ ํฐ ๋ชฉ๋ก์ JSON ํ์์ผ๋ก ๋ฐํํฉ๋๋ค.
return c.json(createResponse(ResponseCode.SUCCESS, "Success", tokens));
@@ -58,6 +63,8 @@ export class UserController {
// deleteFCMTokenV1 ๋ฉ์๋๋ FCM ํ ํฐ์ ์ญ์ ํ๋ API ์๋ํฌ์ธํธ์
๋๋ค.
async deleteFCMTokenV1(c: Context) {
+ // ์ธ์ฆ ๋ฏธ๋ค์จ์ด๋ฅผ ํตํด ๋์ด์จ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
+ const requestUser = c.get("user") as ServiceUser;
// ์์ฒญ์์ fcmToken์ ์ถ์ถํฉ๋๋ค.
const { fcmToken } = await c.req.json();
@@ -74,7 +81,7 @@ export class UserController {
// UserService๋ฅผ ํตํด FCM ํ ํฐ์ ์ญ์ ํฉ๋๋ค.
const result = await this.userService.deleteFCMToken(
- "mock_user_id",
+ requestUser.id,
fcmToken,
);
Diff
๋ณต์ฌ