Python (Flask)
Contoh integrasi OAuth dengan Flask.
# oauth_client.py
import os
import secrets
import hashlib
import base64
from urllib.parse import urlencode
from flask import Flask, redirect, request, session, jsonify
import requests
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
# Konfigurasi
CONFIG = {
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET', # Hanya untuk confidential client
'redirect_uri': 'http://localhost:5000/callback',
# Origin saja — path /api/auth/oauth2/... ditambahkan di bawah.
# Issuer OIDC (untuk validasi iss) = origin + "/api/auth"
'auth_server_url': 'https://bukutamu.tabalongkab.go.id',
'issuer': 'https://bukutamu.tabalongkab.go.id/api/auth',
'scopes': ['openid', 'profile', 'email', 'role', 'organization'],
}
def generate_code_verifier() -> str:
"""Generate random code verifier untuk PKCE"""
return secrets.token_urlsafe(64)[:64]
def generate_code_challenge(code_verifier: str) -> str:
"""Generate code challenge dari code verifier (S256)"""
digest = hashlib.sha256(code_verifier.encode()).digest()
return base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
@app.route('/login')
def login():
"""Mulai OAuth flow"""
# Generate PKCE values
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
state = secrets.token_urlsafe(32)
# Simpan untuk verifikasi nanti
session['oauth_state'] = state
session['code_verifier'] = code_verifier
# Buat authorization URL
auth_params = {
'client_id': CONFIG['client_id'],
'redirect_uri': CONFIG['redirect_uri'],
'response_type': 'code',
'scope': ' '.join(CONFIG['scopes']),
'state': state,
'code_challenge': code_challenge,
'code_challenge_method': 'S256',
}
auth_url = f"{CONFIG['auth_server_url']}/api/auth/oauth2/authorize?{urlencode(auth_params)}"
# Redirect user ke SSO Tabalong
return redirect(auth_url)
@app.route('/callback')
def callback():
"""Handle OAuth callback"""
# Handle error dari OAuth server
error = request.args.get('error')
if error:
return jsonify({
'error': error,
'error_description': request.args.get('error_description'),
}), 400
code = request.args.get('code')
state = request.args.get('state')
# Verifikasi state
if state != session.get('oauth_state'):
return jsonify({'error': 'Invalid state parameter'}), 400
code_verifier = session.get('code_verifier')
# Bersihkan session
session.pop('oauth_state', None)
session.pop('code_verifier', None)
# Tukar code dengan token
token_response = requests.post(
f"{CONFIG['auth_server_url']}/api/auth/oauth2/token",
data={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': CONFIG['redirect_uri'],
'client_id': CONFIG['client_id'],
'client_secret': CONFIG['client_secret'],
'code_verifier': code_verifier,
},
headers={
'Content-Type': 'application/x-www-form-urlencoded',
}
)
if not token_response.ok:
return jsonify(token_response.json()), 400
tokens = token_response.json()
# Ambil user info
userinfo_response = requests.get(
f"{CONFIG['auth_server_url']}/api/auth/oauth2/userinfo",
headers={
'Authorization': f"Bearer {tokens['access_token']}",
}
)
userinfo = userinfo_response.json()
# Simpan di session (di produksi: buat session user, dll)
session['user'] = userinfo
session['tokens'] = tokens
return jsonify({
'tokens': tokens,
'userinfo': userinfo,
})
@app.route('/refresh', methods=['POST'])
def refresh():
"""Refresh access token"""
refresh_token = request.json.get('refresh_token')
response = requests.post(
f"{CONFIG['auth_server_url']}/api/auth/oauth2/token",
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': CONFIG['client_id'],
'client_secret': CONFIG['client_secret'],
},
headers={
'Content-Type': 'application/x-www-form-urlencoded',
}
)
return jsonify(response.json())
@app.route('/userinfo')
def get_userinfo():
"""Ambil user info dari session"""
user = session.get('user')
if not user:
return jsonify({'error': 'Not authenticated'}), 401
return jsonify(user)
if __name__ == '__main__':
app.run(port=5000, debug=True)