Logo

Passport.js로 Bearer 토큰 기반 API 인증 구현하기

이번 포스팅에서는 Passport.js라는 자바스크립트 프레임워크를 사용하여 Bearer 토큰 기반 API 인증을 구현해보겠습니다.

본 포스팅의 예제 코드는 ES 모듈 문법을 사용하여 작성되었습니다. Node.js에서 ES 모듈을 사용하는 방법은 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

Bearer 토큰이란?

Bearer 토큰은 HTTP 요청에서 인증 정보를 전달하는 방법으로 클라이언트가 서버에 접근할 때 인증을 위해 널리 사용됩니다. 일반적으로 클라이언트가 서버에 요청을 보낼 때 HTTP 요청의 Authorization 헤더에 Bearer <인증 토큰>의 형태로 담아서 전송합니다. 서버는 이 인증 토큰 값을 읽고 유효성을 검증한 결과에 따라서 요청을 처리할지 거부할지 판단하게 됩니다.

Bearer 토큰은 OAuth 인증 프로토콜에서 뿐만 아니라 JWT(Json Web Token) 기반 인증에서도 사용되고 있습니다.

OAuth나 JWT에 대한 내용은 OAuth 태그JWT 태그를 참고 바랍니다.

Express.js 패키지 설치

자바스크립트로 서버 애플리케이션을 개발할 때 가장 대중적으로 사용되는 Express.js 프레임워크를 사용하여 예제 코드를 작성하려고 합니다.

따라서 먼저 Express.js를 패키지를 npm을 이용하여 설치해야겠습니다.

$ npm i express

Express.js 서버 앱 작성

Express.js를 이용하여 GET http://localhost:3000/를 요청하면 Hello, World!를 응답하는 초간단 앱을 작성하겠습니다.

import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Hello, World!");
});

app.listen(3000, () => {
  console.log("Server is listening on port 3000");
});

Passport.js 패키지 설치

Passport.js로 Bearer 토큰 기반 인증을 구현하려면 passportpassport-http-bearer 이렇게 2개의 패키지가 필요한데요. Passport.js는 사용자가 설치해야하는 코드를 최소화시킬 수 있도록 모듈화되어 설계되어 있기 때문에 프레임워크 자체에 대한 패키지와 더불어 인증 전략(strategy) 패키지를 별도로 설치해야합니다.

$ npm i passport passport-http-bearer

Passport.js 인증 전략 설정

이제 위에서 설치한 HTTP Bearer 인증 전략을 Passport.js 프레임워크가 사용할 수 있도록 설정해야하는데요. 이를 위해서는 passport.use() 함수에 어떻게 인증을 할지를 구현하여 전달해줘야 합니다.

passport-http-bearer 패키지의 BearerStrategy 클래스의 생성자는 콜백(callback) 함수를 인자로 받는데요. 첫 번째 인자로 HTTP 요청의 Authorization 헤더에 담겨있는 Bearer 토큰을 읽어서 전달해줍니다. (우리가 이 부분을 직접 구현하지 않아도 되니 엄청 편하죠?)

이 콜백 함수의 두 번째 인자인 done() 함수를 이용하여 우리는 토큰이 유효한지 검증하는 코드를 작성할 수 있습니다. 토큰이 유효한 경우에는 done(null, 인증된 사용자 정보)를 호출하고, 토큰이 유효하지 않은 경우에는 done(null, false)를 호출하면 되지요. 뿐만 아니라 토큰 검증 과정에서 예외적인 상황이 발생하면 done() 함수의 첫 번째 인자로 에러 객체를 넘길 수도 있습니다.

Passport.js가 인증 성공 시 done(null, 인증된 사용자 정보)를 통해 전달받은 사용자 정보는 최종적으로 HTTP 요청의 user 속성으로 추가해주게 되어 있습니다. 그러므로 요청 핸들러(handler)에서는 필요하다면 이 사용자 정보를 읽어서 활용할 수 있는 것이지요.

import passport from "passport";
import BearerStrategy from "passport-http-bearer";

passport.use(
  new BearerStrategy((token, done) => {
    if (token === "1234") {
      done(null, { email: "user@test.com" });
    } else {
      done(null, false);
    }
  })
);

최대한 간단한 예제를 위해서 Bearer 토큰이 1234일 때만 토큰이 유효하다고 판단되도록 구현해보았습니다. (말도 안되죠? 😅)

실제 애플리케이션에션에서는 사용하시는 인증 매커니즘에 따라 다양한 방식으로 구현이 될 것입니다. 예를 들어, Bearer 토큰으로 DB를 조회할 수도 있고, OAuth를 사용하고 있다면 원격 전송할 수도 있고, JWT 토큰이라면 디코딩한 후 서명을 검증해야할 것입니다.

Passport.js 미들웨어 사용

다음으로 유효한 Bearer 토큰과 함께 들어온 요청만 처리하도록 Express.js 앱을 수정해줘야 하는데요. 이 작업은 passport.authenticate() 함수를 통해서 얻은 미들웨어(middleware)로 이루어집니다.

passport.authenticate() 함수의 첫 번째 인자로 HTTP Bearer 인증 전략의 이름인 bearer를 넘기고, 두 번째 인자의 session 옵션을 false로 줘서 Passport.js가 사용자 세션을 만들지 않도록 해줍니다. 웹사이트 인증과 달리 API 인증은 일반적으로 클라이언트가 HTTP 요청을 할 때마다 Bearer 토큰을 보내기 때문입니다.

이렇게 passport.authenticate() 함수가 반환한 미들웨어는 Express.js에서 다른 미들웨어를 사용하는 방식과 동일하게 사용할 수 있는데요. 즉, app.use()를 사용하여 여러 라우트(route)에 일괄 적용할 수도 있고, app.get()이나 app.post() 등을 사용하여 특정 라우트에만 적용할 수도 있을 것입니다.

app.get(
  "/api",
  passport.authenticate("bearer", { session: false }),
  (req, res) => res.send(req.user)
);

저는 예제에서 GET /api 라우트만 인증이 되도록 코드를 작성해보았습니다. 이제 /api 경로로 GET 요청이 들어오면 passport.authenticate() 메소드를 통해 Bearer 인증이 시도될 것입니다. 인증에 성공하면 (위에서 done 함수의 두 번째 인자로 반환했었던) 사용자 정보가 응답되고, 인증에 실패하면 Unauthorized가 응답되야 합니다.

API 인증 테스트

터미널에서 curl 커맨드를 사용해서 API 인증이 정상적으로 되는지 간단한 테스트를 진행해보도록 하겠습니다.

터미널 상에서 간편하게 사용할 수 있는 HTTP 클라이언트인 curl 커맨드에 대해서는 관련 포스팅을 참고 바랍니다.

먼저 그냥 API를 호출해보면 인증이 실패하여 401 Unauthorized가 응답되네요.

$ curl -i http://localhost:3000/api
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
WWW-Authenticate: Bearer realm="Users"
Date: Wed, 05 Apr 2023 02:06:37 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 12

Unauthorized%

이번에는 Bearer 토큰으로 빈 문자열을 넘기면 잘못된 요청으로 400 Bad Request가 응답됩니다.

$ curl -H "Authorization: Bearer" -i http://localhost:3000/api
HTTP/1.1 400 Bad Request
X-Powered-By: Express
Date: Wed, 05 Apr 2023 02:08:00 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 11

Bad Request%

이번에는 bearer 토큰으로 1234를 넘겨보니 인증에 성공하여 사용자 정보와 함께 200 OK가 응답됩니다.

$ curl -H "Authorization: Bearer 1234" -i http://localhost:3000/api
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 25
ETag: W/"19-SnBOKjxk++SjUaaeE9z3p1zVT/o"
Date: Wed, 05 Apr 2023 02:09:21 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"email":"user@test.com"}%

마지막으로 bearer 토큰으로 5678를 넘겨보니 인증에 실패하여 401 Unauthorized가 응답되네요.

curl -H "Authorization: Bearer 5678" -i http://localhost:3000/api
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
WWW-Authenticate: Bearer realm="Users", error="invalid_token"
Date: Wed, 05 Apr 2023 02:10:19 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 12

Unauthorized%

전체 코드

실습 프로젝트의 코드는 아래에서 직접 확인하고 실행해볼 수 있습니다.

마치면서

이상으로 Passport.js 프레임워크를 이용하여 Express.js 앱에 Bearer 토큰 기반 API 인증을 적용해보았습니다. 직접 구현하기는 부담스러울 수 있는 API 인증을 Passport.js를 활용하셔서 간단한 코드로 안전하게 구현하실 수 있으셨으면 좋겠습니다.