[DIP] Session, JWT, OAuth

Tech

There was once an incident where a method to bypass user authentication in an internal service spread across the community and became a problem. As I looked into the authentication side of this incident, it left me with an uneasy feeling that, throughout all my development work at the company so far, I had never implemented login and authentication from scratch or given it much thought. That was because most of the services I developed were built on top of the login system provided internally by the company. While looking into this problem, I thought I might gain some new insight into the company's internal login authentication method, so I decided to organize the login authentication methods that are most commonly used.


There are many login authentication methods, but I will dig into the basic information for the three that I consider the most widely used: Session, JWT, and OAuth.

Web-Auth-1



Session

In session-based login authentication, when authentication succeeds through the user's ID/PW, the server creates a session and stores the authentication state in server memory. It then issues the generated session ID to the client as a cookie to maintain authentication.

Through the session ID set as a cookie, subsequent requests from the client are automatically included in the cookie by the browser, which the server uses to determine whether the authentication is valid.



Components

ComponentDescription
Session IDA unique string issued by the server, stored by the client in a cookie
Session objectHolds information such as user ID, permissions, login time, and expiration time
Session storeStored in memory (local), Redis, DB, etc. Must be shared when scaling the server
Set-Cookie headerThe means by which the server delivers the session ID to the client on successful login
Cookie settingsSecurity control via HttpOnly, Secure, SameSite, etc.


Authentication Flow

docker
[User] │ ▼ [Login request (ID/PW)] │ ▼ [Server: authentication success → create session object] │ ▼ [Server: generate session ID → Set-Cookie response] │ ▼ [Browser: store cookie (session ID)] │ ▼ [Cookie automatically included on API requests] │ ▼ [Server: verify user authentication via session ID]


Considerations

As an implementation consideration, for API requests that require an authenticated state, when sending a request to a server on a different domain, the withCredentials: true option is required in order to send the cookie containing the session ID along with the request. Also, because the cookie is automatically sent with every API request, protection against CSRF attacks is needed.

CSRF (Cross-Site Request Forgery): an attack that makes an unintended request be performed in an authenticated state through a forged cross-site request


By default, the SameSite option is set to Strict or Lax to prevent cookies from being sent to other domains. If communication with a server on a different domain is required, a separate token for CSRF defense is generated by the server and an additional secondary verification step is performed to defend against CSRF.

With SameSite: Strict, cookies are not sent on cross-site requests
With SameSite: Lax, cookies are allowed to be sent for some requests such as GET, HEAD, OPTIONS


In addition, to prevent cookie theft, you should set HttpOnly so that scripts cannot access the cookie, and set Secure so that the cookie is only sent over HTTPS communication. Finally, you can specify the domain and path scope for which the cookie is valid so that it is only sent within a restricted scope.



JWT

JWT is a user authentication method that uses a JSON Web Token. When a user successfully logs in with their ID/PW, the server issues a JWT token and returns it to the client in the response.

The client stores the received token and includes it in the Authorization header on subsequent API requests, and the server verifies authentication using the included token.



Components

ComponentDescription
Access TokenToken for authentication and authorization verification, short expiration time
Refresh TokenUsed to renew the Access Token, long expiration time
PayloadContains information such as user ID, permissions, login time, and expiration time
SignatureHMAC or RSA signature using the server's secret key
Storage locationVarious: localStorage, sessionStorage, memory, HttpOnly cookie, etc.


Authentication Flow

docker
[User] │ ▼ [Login request (ID/PW)] │ ▼ [Server: authentication success → issue JWT Access + Refresh Token] │ ▼ [Client: store tokens (localStorage, memory, cookie, etc.)] │ ▼ [On API request, Authorization: Bearer <AccessToken>] │ ▼ [Server: verify token signature and expiration → authenticate] │ └───→ [Access Token expired?] │ ├─ No → authentication success │ └─ Yes → request token reissue with Refresh Token │ ▼ [Return new Access Token]


Considerations

A consideration when implementing JWT is that, for token storage, a method managed in memory or in an HttpOnly cookie is recommended over localStorage, which is vulnerable to XSS. Because the JWT authentication method does not manage state on the server, if a token is stolen, it can be difficult for the server to determine whether it has been compromised. Therefore, for additional security, the token is included in the server signature to prevent forgery.

Server signature (Signature): a digital signature to guarantee that the JWT content has not been forged
The signature is generated using a secret key stored on the server and used as the JWT Signature
JWT structure: [Base64(Header)].[Base64(Payload)].[Signature]


In addition, the expiration time for the Access Token should be set short, and, just as with sessions, strengthening security through options such as HttpOnly and Secure is essential.



OAuth

OAuth (Open Authorization) is a method that delegates authentication handling to an external party. The user selects an authorization server that can authenticate them (Google, Kakao, etc.), and after authentication, receives an authorization code that is exchanged for an Access Token through the server.

The client that receives the token from the server communicates by having its authentication verified with that token, and receives and uses information from the authorization server.



Components

ComponentDescription
Client ID / SecretIdentifiers issued when registering the OAuth app
Authorization CodeA temporary code delivered to the client after authentication
Access Token / Refresh TokenExchanged and used after authentication is complete
Redirect URIThe address to redirect to after successful authentication
StateA random string that verifies the legitimacy of the request (for CSRF prevention)
PKCEA security-hardening mechanism for public clients (SPA, mobile apps) (code_verifier / code_challenge)

PKCE (Proof Key for Code Exchange): an OAuth 2.0 security extension that secures the vulnerability of the authorization code
A random key is generated randomly during the Authorization Code exchange and used as an additional verification key with the server
code_verifier: a random string generated arbitrarily by the client (usually 43–128 characters)
code_challenge: a transformed value of the code_verifier to be stored on the server (SHA256 hashed, then Base64 URL encoded)


Authentication Flow

docker
[Client] │ ▼ [OAuth authentication request → redirect to Provider] │ ▼ [User: login + consent to permissions] │ ▼ [OAuth server → deliver code to Redirect URI] │ ▼ [Client → deliver Authorization Code to server] │ ▼ [Server: request Access Token with code + verifier] │ ▼ [OAuth server: respond with Access Token (and Refresh Token)] │ ▼ [Client: store Access Token] │ ▼ [On API call, Authorization: Bearer <AccessToken>]


Considerations

Compared to other authentication methods, the OAuth approach requires attention to security given its somewhat complex flow. First, authentication page handling is done through the OAuth authentication server. After login, when delivering the code received at the redirection URL to the backend, a process is needed to check whether the redirect has been forged by using the state randomly generated by the client. And when delivering the Authorization Code to the server, PKCE handling is used to prevent theft of the authorization code.

And, just as explained for the other authentication methods, it is best to handle the token generated from the authorization code with options such as HttpOnly and Secure so that it is used in a more secure manner.



Sample Implementation

Based on the content organized above, I will implement Session, JWT, and OAuth as simple actual demos and revisit the organized content once more. For the demo implementation, I performed authentication and login handling within a Node server and managed sessions and tokens; for OAuth, I implemented it with GitHub OAuth, which is simple to set up and free to use.


Session

🔐 Session 인증 데모 (실제 서버 통신)

💡 실제 동작 원리:
1. 로그인 시 Next.js 서버에서 세션 ID를 생성하고 메모리에 저장
2. HttpOnly, Secure, SameSite=Strict 옵션으로 쿠키 설정
3. 세션 ID는 JSON 응답에 포함되지 않고 오직 HttpOnly 쿠키로만 전달 (XSS 방어)
4. 이후 모든 API 요청에 쿠키가 자동으로 포함 (credentials: 'include')
5. 서버에서 쿠키의 세션 ID로 사용자 인증 상태 확인
6. 로그아웃 시 서버의 세션 삭제 및 쿠키 제거
✅ (실제 Next.js 서버 API Routes를 사용한 보안 구현입니다)



JWT

🔐 JWT 인증 데모 (실제 서버 통신)

💡 실제 동작 원리:
1. 로그인 시 서버에서 Access Token (15분)과 Refresh Token (7일) 발급
2. Access Token은 메모리에 저장, Refresh Token은 HttpOnly 쿠키에 저장
3. API 요청 시 Authorization: Bearer 헤더에 Access Token 포함
4. 서버에서 토큰 서명을 검증하여 인증 (HMAC SHA256)
5. Access Token 만료 시 Refresh Token으로 새로운 Access Token 발급
6. 로그아웃 시 Access Token을 블랙리스트에 추가하고 Refresh Token 쿠키 삭제
✅ (실제 Next.js 서버 API Routes를 사용한 JWT 구현입니다)



OAuth

🔐 OAuth 인증 데모 (GitHub + PKCE)

ℹ️ OAuth 로그인 방식

- GitHub 계정으로 로그인합니다
- State를 통한 CSRF 방어
- PKCE를 통한 Authorization Code 보안
- 인증 후 자동으로 이 페이지로 돌아옵니다

💡 실제 동작 원리:
1. 로그인 버튼 클릭 → 서버에서 State와 PKCE Code Verifier/Challenge 생성
2. GitHub 인증 페이지로 리디렉션 (State와 Code Challenge 포함)
3. 사용자가 GitHub에서 로그인 및 권한 승인
4. GitHub가 Authorization Code와 State를 Callback URL로 전달
5. 서버에서 State 검증 (CSRF 방어) 및 Code Verifier로 Token 교환 (PKCE)
6. Access Token으로 GitHub API에서 사용자 정보 조회
7. 서버 세션 생성 및 HttpOnly 쿠키로 저장
✅ (실제 GitHub OAuth API를 사용한 구현입니다)



What Is Authentication?

Authentication handling in a service is one of the important indicators that can give users trust. Various authentication methods exist, but I believe that clearly understanding and adopting the authentication method suitable for each service is the very starting point of building trust.

Using an already proven authentication method is a good approach, but we must always check whether there are any security vulnerabilities we might be missing within our own service. Only then can we build a service that earns even greater trust from users.

While organizing this content, I felt it was important to look back at whether there was anything we missed, and to examine not only the security handling needed on the frontend but also the security issues on the server side. I think that an attitude of not staying confined to the role of a frontend developer, but instead considering and taking care of the security of the service as a whole, will become even more important going forward.

Web-Auth-2

"Trust is built in very small moments." - Brené Brown -