- Implemented OAuth2 server with client registration, authorization, and token endpoints. - Created HTML templates for client authorization, client creation, and client editing. - Developed an OAuth2 client application using Hono.js and Bun, supporting authorization code grant flow. - Integrated PKCE (Proof Key for Code Exchange) for enhanced security during authorization. - Added session management using cookies for user authentication. - Included detailed README documentation for setup and usage instructions.
108 lines
No EOL
3.5 KiB
TypeScript
108 lines
No EOL
3.5 KiB
TypeScript
import { Hono } from 'hono'
|
|
|
|
const app = new Hono()
|
|
|
|
// define config
|
|
const OAUTH_CONFIG = {
|
|
authServerUrl: 'http://localhost:3020',
|
|
clientId: process.env.CLIENT_ID, // 클라이언트 등록 후 설정 필요
|
|
clientSecret: process.env.CLIENT_SECRET, // 필요한 경우
|
|
redirectUri: process.env.REDIRECT_URI,
|
|
scope: 'profile'
|
|
}
|
|
|
|
app.get('/', (c) => {
|
|
|
|
return c.html(`
|
|
<h1>Attacker</h1>
|
|
|
|
<h2>Profile</h2>
|
|
<form action="/profile" method="get">
|
|
<input type="text" name="access_token" placeholder="Access Token" style="width:300px; padding:5px;" required />
|
|
<button type="submit" style="padding:5px 10px;">프로필 보기</button>
|
|
</form>
|
|
`)
|
|
})
|
|
|
|
|
|
app.get("/profile", async (c) => {
|
|
const accessToken = c.req.query('access_token')
|
|
|
|
try {
|
|
// 바로 프로필 조회
|
|
const profileResponse = await fetch(`${OAUTH_CONFIG.authServerUrl}/api/me`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`
|
|
}
|
|
})
|
|
|
|
if (!profileResponse.ok) {
|
|
return c.html(`
|
|
<h1>프로필 조회 실패</h1>
|
|
<p>토큰은 발급되었지만 프로필 조회에 실패했습니다.</p>
|
|
<p>상태 코드: ${profileResponse.status}</p>
|
|
<a href="/">홈으로</a>
|
|
`)
|
|
}
|
|
|
|
const profile = await profileResponse.json() as any
|
|
|
|
// 토큰 정보와 프로필을 함께 표시
|
|
return c.html(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>인증 완료 - 프로필</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
.container { max-width: 800px; margin: 0 auto; }
|
|
.button { background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block; margin: 10px 5px; }
|
|
.success { color: #28a745; font-size: 18px; margin-bottom: 20px; }
|
|
.section { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; border-left: 4px solid #007bff; }
|
|
.profile-item { margin: 10px 0; }
|
|
.label { font-weight: bold; color: #495057; }
|
|
.value { color: #212529; }
|
|
.token-info { background: #e3f2fd; border-left-color: #2196f3; }
|
|
.profile-info { background: #e8f5e8; border-left-color: #28a745; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="section profile-info">
|
|
<h3>👤 사용자 프로필</h3>
|
|
<div class="profile-item">
|
|
<span class="label">사용자 ID:</span>
|
|
<span class="value">${profile.id || 'N/A'}</span>
|
|
</div>
|
|
<div class="profile-item">
|
|
<span class="label">사용자명:</span>
|
|
<span class="value">${profile.username || 'N/A'}</span>
|
|
</div>
|
|
<div class="profile-item">
|
|
<code>
|
|
${JSON.stringify(profile, null, 2)}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 30px;">
|
|
<a href="/" class="button">홈으로</a>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`)
|
|
|
|
} catch (error: any) {
|
|
return c.html(`
|
|
<h1>오류 발생</h1>
|
|
<p>토큰 교환 또는 프로필 조회 중 오류가 발생했습니다: ${error?.message || '알 수 없는 오류'}</p>
|
|
<a href="/">홈으로</a>
|
|
`)
|
|
}
|
|
})
|
|
|
|
export default {
|
|
port: 5002,
|
|
fetch: app.fetch,
|
|
}
|