Search
📚

EP15. Supabase Auth Middleware

생성일
2024/06/06 07:44
마지막 업데이트
2024/06/09

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
복사

강의 코드

7
pull