Q> OAuth로 인증을 설계 및 구현할때 보안고려사항에 대한 문의

회사에서 개발업무를 담당하고 있습니다. 기존에 운영하던 서비스에 OAuth 인증기능을 추가하려고 합니다.
설계나 구현할때 보안상 고려해야 햐는 사항은 무엇이 있나요?

A>

<> 보안 고려사항

1. 통신구간 암호화를 사용한다.
- 통신상의 도청, 변조 공격을 방지하기 위하여 Client와 OAuth server간의 모든 통신구간을 HTTP/TLS로 암호화를 적용한다.(TLS1.2이상)
- MiTMA(main in the middl attack)을 방지하기 위해서는 OAuth Server의 인증서를 Client에서 검증할 수 있도록 E2E암호화를 적용한다.

2. 안전하게 Client Secrets(Client ID에 대한 비밀키)을 저장한다.
- Client secrets는 client측에 안전하게 저장되어야 하며 평문으로 전송되거나 코드내에 포함되어 있어서는 안된다.
- 비인가접근으로 부터 client secrets를 보호하기 위하여 암호화 또는 안전한 키관리를 사용한다.

3. 안전한 Token storage를 사용한다.
- 비인가접근으로 부터 token을 보호하기 위하여 client측에 암호화 등으로 안전하게 저장되어야 한다.
- 안전한 저장 메커니즘으로써 암호화된 로컬 저장 또는 Secure HTTP-only cookies를 고려한다.

4. state parameter를 구현한다.
- state parameter는 CSRF공격을 방지하기 위하여 OAuth 인증요청에서 사용되어야 한다.
- state parameter는 CSRF 공격을 방지하기 위해 redirect response에서 변경되지 않은상태로 리턴되고 client 측에서 검증되어야 한다.

5. 적절한 인증절차를 구현한다.
- 인가자만 접근하도록 적합한 사용자 인증, 동의 절차 등을 포함한 인증 절차를 구현한다.
- 사용자가 적절하게 인증되고 권한부여되었는지 확인하는 절차를 구현한다.

6. Token을 검증한다.
- token은 Server측에서 signature 확인과 만료여부 확인 등을 검증해야 한다.
- token은 비인가자의 접근을 방지하기 위하여 인증성에 대하여 검증되어야 한다.

7. 접근허용 범위를 제한한다.
- toaken의 허용범위를 요청된 범위로만 접근을 제한하도록 한다.
- 사용자 데이터에 대한 무단 접근을 초래할 수 있는 광범위하거나 무제한적인 접근 허용 범위를 부여하지 않는다.

8. 모니터링 및 기록관리
- 사용자 데이터에 대한 접근을 추적하고 감사하기 위해 모니터링 및 로깅기능을 구현한다.
- 모든 OAuth권한 요청과 접근을 모니터링하고 로그기록을 남긴다.

9. 적절한 Error handling을 한다.
- 정보노출과 무단접근을 방지하기 위하여 적절한 Error Handling을 구현한다.
- Error 메시지에서 client secrets과 같은 중요정보가 노출되지 않도록 한다.

10. 정기적 검토 및 업데이트
- 신규 보안위협과 취약점을 해결하기 위하여 정기적으로 검토하고 업데이트한다.
- OAuth보안 관련 최신 모범사례를 가지고 필요한 변경사항을 구현하고 최신상태로 유지한다.

<> Flask 프레임워크를 이용한 OAuth 인증 Python 예제 코드

flask 프레임워크로 OAuth 2.0을 구현한 Python 예제 코드

from flask import Flask, redirect, request
import requests

app = Flask(__name__)

client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
redirect_uri = "YOUR_REDIRECT_URI"
auth_uri = "https://oauth.example.com/authorize"
token_uri = "https://oauth.example.com/token"

@app.route("/")
def index():
    return redirect(f"{auth_uri}?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code")

@app.route("/callback")
def callback():
    code = request.args.get("code")
    if code:
        data = {
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": redirect_uri,
            "client_id": client_id,
            "client_secret": client_secret
        }
        try:
            response = requests.post(token_uri, data=data)
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            return "Error while obtaining access token: " + str(e)
        access_token = response.json().get("access_token")
        if access_token:
            # access token을 안전하게 저장, 사용
            return "Access token obtained successfully."
    return "Failed to obtain access token."

if __name__ == "__main__":
    app.run(debug=True)