OAuth 2.0 + OpenID Connect สำหรับระบบ Partner
SSO Horizon · https://sso.doae.go.th
SSO Horizon เป็นระบบยืนยันตัวตนกลาง (Single Sign-On) ของกรมส่งเสริมการเกษตร ใช้มาตรฐาน OAuth 2.0 + OpenID Connect (OIDC) ให้ระบบภายนอกเชื่อมต่อ login ร่วมกันได้
ก่อน integrate ต้องขอ OAuth Client จาก SSO Admin โดยเตรียมข้อมูล:
| รายการ | ตัวอย่าง |
|---|---|
| ชื่อระบบ | DOAE APP / ระบบรายงาน X |
| Production URL | https://app.doae.go.th |
| Redirect URI | https://app.doae.go.th/auth/callback |
| Scopes | openid profile email |
| ผู้ติดต่อ | dev@partner.co.th |
หลัง Admin อนุมัติ จะได้รับ client_id และ client_secret
หรือขอผ่าน Developer Portal (login SSO ก่อน → เมนู Developer Portal → ขอ OAuth Client → รอ Admin approve)
ใส่ใน .env ฝั่ง Server เท่านั้น — ห้าม commit ลง git
# === SSO OAuth2 (ฝั่ง Server — ห้าม commit) === SSO_ENABLED=true SSO_ISSUER=https://sso.doae.go.th SSO_API_URL=https://sso.doae.go.th SSO_PORTAL_URL=https://sso.doae.go.th SSO_CLIENT_ID=<client_id จาก SSO Admin> SSO_CLIENT_SECRET=<client_secret จาก SSO Admin> SSO_REDIRECT_URI=https://your-app.doae.go.th/auth/callback SSO_SCOPES=openid profile email APP_URL=https://your-app.doae.go.th
client_secret ห้ามใส่ใน frontend, mobile app หรือ JavaScript ที่ client เห็นได้Base URL ทุก endpoint: https://sso.doae.go.th
| Method | Path | ใช้เมื่อ |
|---|---|---|
| GET | /.well-known/openid-configuration | Auto-config OIDC |
| GET | /oauth/authorize | เริ่ม login — redirect browser |
| POST | /oauth/token | แลก code → token (backend) |
| GET | /oauth/userinfo | ดึง profile (Bearer token) |
| GET | /api/auth/me | profile ฉบับเต็ม (Bearer token) |
| POST | /api/auth/refresh | Refresh access token |
| POST | /api/auth/logout | Revoke session |
| GET | /health | Health check |
Discovery: https://sso.doae.go.th/.well-known/openid-configuration
/oauth2/* เป็น legacy — ใช้ /oauth/* เท่านั้นสร้าง state แบบ random เก็บใน session แล้ว redirect:
https://sso.doae.go.th/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fyour-app.doae.go.th%2Fauth%2Fcallback&response_type=code&scope=openid%20profile%20email&state=RANDOM_STATE
GET https://your-app.doae.go.th/auth/callback?code=UUID&state=RANDOM_STATE
state ต้องตรงกับ session → ไม่ตรง = 403code ใช้ครั้งเดียว หมดอายุ 5 นาทีPOST https://sso.doae.go.th/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTHORIZATION_CODE &redirect_uri=https://your-app.doae.go.th/auth/callback &client_id=YOUR_CLIENT_ID &client_secret=YOUR_CLIENT_SECRET
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 86400
}GET https://sso.doae.go.th/oauth/userinfo Authorization: Bearer ACCESS_TOKEN
{
"sub": "df5ddfbd-2ccf-4eb9-bc66-a843c031b335",
"id": "df5ddfbd-2ccf-4eb9-bc66-a843c031b335",
"email": "user@doae.go.th",
"name": "นายทดสอบ ระบบ",
"full_name": "นายทดสอบ ระบบ",
"preferred_username": "user",
"role": "admin",
"role_id": "uuid-from-roles-table",
"organization_id": "uuid",
"organization_name": "สำนักงานเกษตรอำเภอ...",
"organization_type": "district",
"province": "เชียงใหม่",
"region_id": "uuid-org-unit-region",
"province_id": "uuid-org-unit-province",
"district_id": "uuid-org-unit-district",
"center_id": null,
"admin_level": "district"
}| Field | ใช้ทำอะไร |
|---|---|
| sub / id | Primary Key ใน App (sso_user_id) — ใช้ UUID นี้เป็นหลัก |
| อีเมลราชการ @doae.go.th | |
| full_name / name | ชื่อ-นามสกุล |
| role | ชื่อ role: user | technical_support | admin | superadmin | developer |
| role_id | UUID จากตาราง roles (primary key ของ role) |
| organization_id | UUID หน่วยงานที่ผู้ใช้สังกัด |
| organization_name | ชื่อหน่วยงาน |
| organization_type | ประเภทหน่วยงาน เช่น region, province, district, center |
| province | ชื่อจังหวัด (string) |
| region_id | UUID หน่วยงานระดับ สสก (region) |
| province_id | UUID หน่วยงานระดับจังหวัด |
| district_id | UUID หน่วยงานระดับอำเภอ |
| center_id | UUID หน่วยงานระดับศูนย์ |
| admin_level | ขอบเขตสิทธิ์แอดมิน — ต้องมอบหมายก่อน: central | region | province | district | center |
ไม่ได้มาจาก role อัตโนมัติ — ต้องมอบผ่านหน้า มอบหมายสิทธิ์ ใน SSO Portal
| ค่า | ความหมาย | Filter ข้อมูลด้วย | มอบต่อได้ |
|---|---|---|---|
| central | ส่วนกลาง | ทั้งกรม | region → center |
| region | สสก | region_id | province → center |
| province | จังหวัด | province_id | district, center |
| district | อำเภอ | district_id | center |
| center | ศูนย์ | center_id | — |
| (ไม่มี field) | ยังไม่ถูกมอบหมาย | organization_id ของตัวเอง | — |
role=admin ให้ผู้รับ และส่ง admin_level ใน userinfosuperadmin เองได้ admin_level=central อัตโนมัติ/api/auth/me ให้ field ชุดเดียวกัน เพิ่ม: personal_email, work_email, phone, organization_legacy_id
Authorization: Bearer {token}แนะนำ validate โดยเรียก /oauth/userinfo — ไม่ต้อง verify JWT signature เอง
POST https://sso.doae.go.th/api/auth/refresh
Content-Type: application/json
{"refresh_token": "YOUR_REFRESH_TOKEN"}{
"success": true,
"tokens": {
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 86400
}
}POST /api/auth/logout ด้วย Bearer token (optional)https://sso.doae.go.th/login| Error | สาเหตุ | วิธีแก้ |
|---|---|---|
| invalid_client | client_id/secret ผิด | ตรวจ .env |
| invalid_redirect_uri | URI ไม่ตรงที่ลงทะเบียน | ตรวจ trailing slash, http/https |
| invalid_grant | code หมดอายุ/ใช้แล้ว | login ใหม่ |
| invalid_scope | scope ไม่ได้รับอนุญาต | ใช้ openid profile email |
| access_denied | user ปฏิเสธ | แจ้ง user |
ต้องทำ (Must)
ห้ามทำ (Must Not)
// routes/web.php
Route::get('/auth/login', [SsoController::class, 'login']);
Route::get('/auth/callback', [SsoController::class, 'callback']);
Route::post('/auth/logout', [SsoController::class, 'logout']);
// SsoController — login
$state = Str::random(40);
session(['oauth_state' => $state]);
return redirect('https://sso.doae.go.th/oauth/authorize?' . http_build_query([
'client_id' => config('sso.client_id'),
'redirect_uri' => config('sso.redirect_uri'),
'response_type' => 'code',
'scope' => 'openid profile email',
'state' => $state,
]));
// SsoController — callback
if ($request->state !== session('oauth_state')) abort(403);
$tokens = Http::asForm()->post('https://sso.doae.go.th/oauth/token', [
'grant_type' => 'authorization_code',
'code' => $request->code,
'redirect_uri' => config('sso.redirect_uri'),
'client_id' => config('sso.client_id'),
'client_secret' => config('sso.client_secret'),
])->json();
$user = Http::withToken($tokens['access_token'])
->get('https://sso.doae.go.th/oauth/userinfo')->json();
// ใช้ $user['sub'] เป็น sso_user_id ใน DBapp.get('/auth/login', (req, res) => {
const state = crypto.randomBytes(32).toString('hex');
req.session.oauthState = state;
const params = new URLSearchParams({
client_id: process.env.SSO_CLIENT_ID,
redirect_uri: process.env.SSO_REDIRECT_URI,
response_type: 'code',
scope: 'openid profile email',
state,
});
res.redirect('https://sso.doae.go.th/oauth/authorize?' + params);
});
app.get('/auth/callback', async (req, res) => {
if (req.query.state !== req.session.oauthState) return res.status(403).send('Invalid state');
const { data: tokens } = await axios.post('https://sso.doae.go.th/oauth/token',
new URLSearchParams({
grant_type: 'authorization_code',
code: req.query.code,
redirect_uri: process.env.SSO_REDIRECT_URI,
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET,
}).toString(),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
const { data: user } = await axios.get('https://sso.doae.go.th/oauth/userinfo', {
headers: { Authorization: 'Bearer ' + tokens.access_token },
});
req.session.user = user;
res.redirect('/dashboard');
});Q: API อยู่ domain ไหน?
A: Production ใช้ https://sso.doae.go.th ทุก endpoint (Portal + API + OAuth รวมกัน)
Q: User ต้องมี account SSO ก่อนไหม?
A: ใช่ — สมัครที่ /register หรือ Admin สร้างให้
Q: Primary key ใช้อะไร?
A: sub (UUID) — ไม่ใช้ email อย่างเดียว
Q: province_id / district_id ได้จากไหน?
A: คำนวณจากต้นไม้ organization_units — มี region_id / center_id ด้วย
Q: admin_level คืออะไร?
A: ขอบเขตสิทธิ์ที่มอบหมาย: central | region | province | district | center — ต้องมอบก่อนถึงจะมีใน userinfo
Q: ใครกำหนด admin_level ได้?
A: Superadmin มอบก่อน → Admin มอบต่อให้ สสก/จังหวัด/อำเภอ/ศูนย์ ตามระดับ (หน้ามอบหมายสิทธิ์)
Q: ต้อง implement 2FA เองไหม?
A: ไม่ — SSO จัดการให้ตอน login
Q: เปลี่ยน callback path ได้ไหม?
A: ได้ — แจ้ง SSO Admin เพิ่ม redirect URI ก่อน deploy
Q: ทดสอบ local ยังไง?
A: ใช้ http://localhost:3000/auth/callback (ต้องลงทะเบียน URI นี้กับ Admin)
Q: client_secret หลุดทำยังไง?
A: แจ้ง SSO Admin regenerate ทันที
SSO Admin / ขอ OAuth Client: helpdesk@doae.go.th
SSO Portal: https://sso.doae.go.th
หน่วยงาน: ศูนย์เทคโนโลยีสารสนเทศและการสื่อสาร กรมส่งเสริมการเกษตร
สารบัญ
กรมส่งเสริมการเกษตร · SSO Horizon · อัปเดต มิ.ย. 2026