AppAuthen
AtlasX Web Service มีเซอร์วิสสำหรับ Authentication โดยนำ OAuth 2.0 มาพัฒนาเพื่อให้รองรับการ Authentication จาก Client App ที่หลากหลายมากขึ้น เช่น Client Mobile App เป็นต้น
ประเภท Authentication นั้นสามารถเลือกได้ 3 แบบ ได้แก่
Firstคือ ผู้ใช้สามารถ Login เพียงเครื่องเดียว และให้ความสำคัญกับผู้ที่ Login ก่อนเป็นสำคัญ ผู้ใช้งานคนเดิม Login เข้ามาซ้ำจะไม่สามารถ Login ได้Lastคือ ผู้ใช้สามารถ Login เพียงเครื่องเดียว และให้ความสำคัญกับผู้ที่ Login ทีหลังเป็นสำคัญ ผู้ใช้งานคนเดิม Login เข้ามาซ้ำ ผู้ใช้ก่อนหน้าจะถูก Logout โดยอัตโนมัติMultipleคือ ผู้ใช้งานเดียวสามารถ Login ได้หลายเครื่องพร้อมกัน (ค่าเริ่มต้น)
วิธีเปลี่ยนประเภท Authentication
- เปิดไฟล์
Core/AppSettings.cs - เลื่อนไปที่คลาส
OAuth - ปรับแก้ property
Strategyตามต้องการ
public class OAuth : IOAuth
{
...
public RefreshTokenStrategy Strategy { get; } = RefreshTokenStrategy.Multiple;
}การตั้งค่าสำหรับ OAuth 2.0
ในไฟล์ appsettings.json มีส่วนของการตั้งค่า OAuth 2.0 อยู่ในส่วนของ WebServiceSettings ดังนี้
OAuth
{
"WebServiceSettings": {
"OAuth": {
"AccessTokenExpires": 300,
"RefreshTokenExpires": 604800,
"AuthorizationCodeExpires": 300,
"Issuer": "https://localhost:5001",
"SecretKey": "kaq5ehRJFtPZrUBOhpif4U6+cuqpAkLCAds8K52FkuXyIcgBCkeBRBchU+b/I5xz"
}
}
}AccessTokenExpiresคือ อายุ Access Token มีหน่วยเป็นวินาที หากตั้งค่าไว้น้อย จะเพิ่มความปลอดภัย แต่ถ้าหากตั้งไว้มากจะลดภาระเครื่อง ServerRefreshTokenExpiresคือ อายุ Refresh Token มีหน่วยเป็นวินาที ใช้ในกรณีที่ต้องการให้ User ทำการ Login ใหม่หากไม่ได้ใช้งานเกิดที่กำหนดAuthorizationCodeExpiresคือ อายุ Authorization CodeIssuerคือ url ที่ให้บริการ OAuth 2.0SecretKeyคือ รหัสลับสำหรับใช้เพื่อสร้าง Access Token
LDAP
{
"WebServiceSettings": {
"LDAP": {
"Host": "cdg.co.th",
"Port": 389,
"SecureSocketLayer": false,
"DistinguishedName": "OU=GISC,OU=CDG,DC=cdg,DC=co,DC=th",
"AdminUser": "00xxxx@cdg.co.th",
"AdminPassword": "...",
"UserIdField": "samaccountname",
"UsernameField": "samaccountname",
"FirstNameField": "givenname",
"LastNameField": "sn",
"MailField": "mail"
}
}
}Hostคือ เครื่องที่ให้บริการ LDAPPortคือ Port ของเครื่องที่ให้บริการ LDAPSecureSocketLayerคือ ระบุว่าเครื่อง LDAP รองรับ SSL หรือไม่DistinguishedName(DN) คือ ชื่อของผู้ใช้ที่มีสิทธิเรียกใช้งาน AD โดย username นั้น อย่างน้อยต้องอยู่ในกลุ่มของ Domain Admin ขึ้นไปAdminUserคือ user ที่เป็น adminAdminPasswordคือ รหัสผ่านของ user ที่เป็น adminUserIdFieldคือ key ที่เป็น Field User IdUsernameFieldคือ key ที่เป็น Field UsernameFirstNameFieldคือ key ที่เป็น Field ชื่อLastNameFieldคือ key ที่เป็น Field นามสกุลMailFieldคือ key ที่เป็น Field Email Address
ตั้งค่าการใช้งานการทำ Authentication
ที่ไฟล์ Startup.cs จะมีการทำ dependency injection สำหรับ Authentication เข้าไปใน service ดังนี้
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register services
services.AddSingleton<IUserTokenRepository, UserTokenRepository>();
services.AddSingleton<IUserInfoRepository, UserInfoRepository>();
}
}ซึ่งจะมี 2 interface ที่ต้องทำการ implement ดังนี้
1. IUserTokenRepository
ระบุการวิธีการเก็บข้อมูล Token เช่น Refresh Token, FCM Token เป็นต้น โดยการเก็บข้อมูลมี 2 แบบดังนี้
UserTokenRepositoryเป็นการเก็บ Token ไว้ที่ Database
services.AddSingleton<IUserTokenRepository, UserTokenRepository>();UserTokenInMemoryRepositoryเป็นการเก็บ Token ไว้ที่ Memory เมื่อ Application Pool ถูกรีสตาร์ท ข้อมูลใน Memory จะสูญหาย เหมาะสำหรับแอพที่ไม่มี Database แต่ต้องการมีระบบ Authentication
services.AddSingleton<IUserTokenRepository, UserTokenInMemoryRepository>();2. IUserInfoRepository
ระบุแหล่งข้อมูลของ User เมื่อมีการ Login ว่าจะให้ตรวจสอบข้อมูล Authentication จากแหล่งไหน โดยการเก็บข้อมูลมี 4 แบบดังนี้
UserInfoRepositoryตรวจสอบจากตารางUM_USERใน Database
services.AddSingleton<IUserInfoRepository, UserInfoRepository>();UserInfoLdapRepositoryตรวจสอบข้อมูลจาก LDAP
services.AddSingleton<IUserInfoRepository, UserInfoLdapRepository>();UserInfoFakeRepositoryตรวจสอบข้อมูลจากการ Mockup User เหมาะสำหรับแอพที่ไม่มี Database แต่ต้องการมีระบบ Authentication
services.AddSingleton<IUserInfoRepository, UserInfoFakeRepository>();UserInfoMultiSourceRepositoryตรวจสอบข้อมูลจากหลาย ๆ data source ไม่ว่าจะเป็น Database, LDAP หรือ Mockup User
services.AddSingleton<IUserInfoRepository, UserInfoMultiSourceRepository>();ซึ่งในการใช้งาน UserInfoMultiSourceRepository จะต้องมีการทำ dependency injection repository อื่น ๆ เข้าไปที่ไฟล์ Startup.cs ด้วย ดังตัวอย่างด้านล่าง
services.AddSingleton<UserInfoLdapRepository>();
services.AddSingleton<UserInfoRepository>();
services.AddSingleton<UserInfoFakeRepository>();
services.AddSingleton<IUserInfoRepository, UserInfoMultiSourceRepository>();Customize Multi Source Repository
ในการ custom การทำ authentication แบบ multi source ให้ไปแก้ไขในไฟล์ OAuth/Repositories/UserInfoMultiSourceRepository.cs เพื่อกำหนด Logic ในการทำ authentication ด้วยตัวเอง
- inject repository ที่จะใช้ในการทำ authentication ลงไปใน constructor
// Inject repositories here
private readonly UserInfoLdapRepository _userInfoLdapRepository;
private readonly UserInfoRepository _userInfoRepository;
public UserInfoMultiSourceRepository(
UserInfoLdapRepository userInfoLdapRepository,
UserInfoRepository userInfoRepository
)
{
_userInfoLdapRepository = userInfoLdapRepository;
_userInfoRepository = userInfoRepository;
}- กำหนดลำดับในการ authentication ใน method ที่ implements มาจาก
IUserInfoRepository
public interface IUserInfoRepository
{
UserInfo Get(string username, string password, string dataSource);
UserInfo Get(int userId);
UserInfo Get(string username);
}รูปแบบการนำไปใช้งาน
Grant Type : Password (Resource Owner Password Credentials)
ผู้ใช้งานส่งรหัสผ่านของตนเองผ่าน Client App ซึ่งเป็นวิธีปกติทั่วไปที่พวกเราคุ้นเคย โดยเราสร้างฟอร์มให้ผู้ใช้งานกรอกรหัสผู้ใช้งานและรหัสผ่าน จากนั้นเรานำค่าที่ได้จากฟอร์ม Request ไปยังเว็บเซอร์วิสโดยตรง

- User: กรอกชื่อผู้ใช้และรหัสผ่าน – ผู้ใช้จำเป็นต้องกรอกข้อมูลรหัสผู้ใช้และรหัสผ่านผ่านหน้า เข้าสู้ระบบของ Client App โดยตรง
- Client App: ร้องขอ Access Token โดยส่งรหัสผู้ใช้และรหัสผ่านไปยังเว็บเซอร์วิส
| Parameter | Required | Description |
|---|---|---|
| grant_type | true | ระบุเป็น password |
| username | true | รหัสผู้ใช้ |
| password | true | รหัสผ่านผู้ใช้ |
| client_id | false | ควรเป็น text ที่ไม่สามารถคาดเดาได้ง่าย |
ตัวอย่างการเรียก
curl --location --request POST 'https://portal-atlasx.cdg.co.th/axws-demo/api/appauthen/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=username1' \
--data-urlencode 'password=1234' \
--data-urlencode 'client_id=696b4176abb7d'- Web Service: ตอบกลับ Access Token หลังจาก Web Service ตอบกลับข้อมูลมาแล้ว Client จะทำการเก็บไว้ที่ Local Storage
| Key | Description |
|---|---|
| access_token | token สำหรับการร้องขอ Resource |
| token_type | ประเภท token |
| expires_in | อายุของ Access Token ในหน่วยวินาที |
| refresh_token | token สำหรับร้องขอ Access Token ใหม่เมื่อหมดอายุ |
| error | รหัสข้อผิดพลาด |
| error_description | คำอธิบายข้อผิดพลาด |
ตัวอย่างการตอบกลับ
- HTTP Status:
200 OK
{
"access_token": "eyJhbGciO...",
"token_type": "bearer",
"expires_in": 20,
"refresh_token": "2469d199696..."
}
- HTTP Status:
400 Bad Request
{
"error": "invalid_grant",
"error_description": "The username or password is incorrect."
}- Client App: Refresh Token เมื่อ Access Token ปัจจุบันที่ Client App หมดอายุลง
| Parameter | Required | Description |
|---|---|---|
| grant_type | true | ระบุเป็น refresh_token |
| refresh_token | true | token ที่ใช้สำหรับร้องขอ Access Token ใหม่ |
| client_id | false | ควรเป็น text ที่ไม่สามารถคาดเดาได้ง่าย |
ตัวอย่างการเรียก
curl --location --request POST 'https://portal-atlasx.cdg.co.th/axws-demo/api/appauthen/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=22d3bff351a94875a95fa7b8e10e4d0b' \
--data-urlencode 'client_id=696b4176abb7d'
- Web Service: ตอบกลับ Access Token
หลังจาก Web Service ตอบกลับข้อมูลมาแล้ว Client ทำการอัพเดทที่ Local Storage
| Key | Description |
|---|---|
| access_token | token สำหรับการร้องขอ Resource |
| token_type | ประเภท token |
| expires_in | อายุของ Access Token ในหน่วยวินาที |
| refresh_token | token สำหรับร้องขอ Access Token ใหม่เมื่อหมดอายุ |
ตัวอย่างการตอบกลับ
- HTTP Status:
200 OK
{
"access_token": "eyJhbGciO...",
"token_type": "bearer",
"expires_in": 20,
"refresh_token": "2469d199696..."
}- HTTP Status:
400 Bad Request
{
"error": "invalid_grant",
"error description": "Invalid refresh_token or expired."
}ตารางรหัสข้อผิดพลาด
| HTTP Status Code | Error Code | Description |
|---|---|---|
| 400 | unsupported_grant_type | พารามิเตอร์ grant_type ไม่ถูกต้อง หรือ ยังไม่รองรับ |
| 400 | invalid_grant | รหัสผู้ใช้หรือรหัสผ่านไม่ถูกต้อง, Refresh Token หมดอายุหรือไม่ถูกต้อง |
| 400 | invalid_request | ไม่ระบุหรือระบุพารามิเตอร์ไม่ถูกต้อง |
| 401 | – | ไม่มีสิทธิ์ในการเข้าถึงข้อมูลหรือ Access Token ไม่ถูกต้อง |
Grant Type : Authorization Code with PKCE (Recommended)

- Access App : ผู้ใช้งานกดปุ่มเข้าสู้ระบบ (Sign In) ที่ Client App โดย Client App สร้างค่าแบบสุ่ม เพื่อใช้เป็น
code_verifier (v)และจะถูก Hash เป็นcode_challenge ($)
ตัวอย่างการสร้างปุ่มล็อกอินด้วย HTML
<a
href="https://localhost:5001/api/appauthen/authorize?
response_type=code&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_CALLBACK_URL&
state=xyzABC123"
>
Sign In
</a>- Redirect with $ : Client App เปิด Web Browser พร้อมกับ Redirect ไปร้องขอ Authorization Code โดยแนบ $ ไปด้วย
| Parameter | Required | Description |
|---|---|---|
| response_type | true | ระบุเป็น code |
| redirect_uri | true | Uri ที่ต้องการให้ Redirect กลับไปที่ App หรือ Web |
| code_challenge | true | ผลลัพท์ hash code_verifier |
| code_challenge_method | true | ระบุเป็น S256 |
| state | false | (แนะนำ) text ที่จะถูกส่งกลับไปให้ Client เมื่อ Redirect กลับไปที่ App หรือ Web เพื่อป้องกันการโจมตีแบบ Cross-site Request Forgery (CSRF) |
| client_id | false | ควรเป็น text ที่ไม่สามารถคาดเดาได้ง่าย |
ตัวอย่างการเรียก
curl --location --request GET 'https://portal-atlasx.cdg.co.th/axws-demo/api/appauthen/authorize?client_id=696b4176abb7d&code_challenge=FC93C580004199BC60CBDC878486F227382A20A8D55DDB109E44AE2F5EA7896F&code_challenge_method=S256&redirect_uri=https://localhost:5001/applogin&response_type=code&state=state123'- Redirect to Login with $ : Web Browser เรียกไปที่ Authorization Server และ Server ทำการเก็บ $ ไว้ จากนั้นจะทำการตรวจสอบว่าผู้ใช้อยู่ในระบบอยู่แล้วหรือไม่ หากไม่อยู่ในระบบจะ Redirect เพื่อไปหน้า Login (ข้อ 4) หากอยู่ในระบบอยู๋แล้ว จะ Redirect พร้อมส่ง Authorization Code กลับไปที่ Web Browser (ข้อ 6)
- Returns to Login Form : Authorization Server ทำการ Redirect ไปหน้า Login
- Submits Credentials : ผู้ใช้ทำการกรอก Username และ Password และ submit ไปยัง Authorization Server
- Redirect with code (a) : Authorization Server ทำการ redirect กลับไป Web Browser พร้อมข้อมูล Authorization Code (a)
- Redirect to App with code (a) : Web Browser จะทำการ redirect เพื่อส่งข้อมูล Authorization Code ไปยัง Client App ผ่านทาง Url
ตัวอย่างการ Redirect กลับไป Client App
- Web App
HTTP/1.1 302 Found
Location:http://localhost:4200/callback?code=ed25980...&state=xyzABC123- Android App
HTTP/1.1 302 Found
Location: com.giscompany.myapp://authorize?code=ed25980...&state=xyzABC123- Request Token (Authorization Code Exchange)
| Parameter | Required | Description |
|---|---|---|
| grant_type | true | ระบุเป็น authorization_code |
| code_verifier | true | code ที่ Client App สร้างไว้ (ข้อ 1) |
| code | true | Authorization Code (a) |
| redirect_uri | true | จะต้องเหมือนกับ (ข้อ 2) |
| client_id | false | ควรเป็น text ที่ไม่สามารถคาดเดาได้ง่าย |
ตัวอย่างการเรียก
curl --location --request POST 'https://portal-atlasx.cdg.co.th/axws-demo/api/appauthen/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=696b4176abb7d' \
--data-urlencode 'code_verifier=C60CBDC878486F' \
--data-urlencode 'code=a93788bc764d441a8479299f4c22a897' \
--data-urlencode 'redirect_uri=https://localhost:50001/applogin'หลังจากได้ Token Response มาก็สามารถเข้าสู่กระบวนการเก็บเก็บลง Local Storage เหมือนกับขั้นตอน Refresh Token ของ Grant Type : Password ได้เลย