AppAuthen

AppAuthen

AtlasX Web Service มีเซอร์วิสสำหรับ Authentication โดยนำ OAuth 2.0 มาพัฒนาเพื่อให้รองรับการ Authentication จาก Client App ที่หลากหลายมากขึ้น เช่น Client Mobile App เป็นต้น

ประเภท Authentication นั้นสามารถเลือกได้ 3 แบบ ได้แก่

  1. First คือ ผู้ใช้สามารถ Login เพียงเครื่องเดียว และให้ความสำคัญกับผู้ที่ Login ก่อนเป็นสำคัญ ผู้ใช้งานคนเดิม Login เข้ามาซ้ำจะไม่สามารถ Login ได้
  2. Last คือ ผู้ใช้สามารถ Login เพียงเครื่องเดียว และให้ความสำคัญกับผู้ที่ Login ทีหลังเป็นสำคัญ ผู้ใช้งานคนเดิม Login เข้ามาซ้ำ ผู้ใช้ก่อนหน้าจะถูก Logout โดยอัตโนมัติ
  3. Multiple คือ ผู้ใช้งานเดียวสามารถ Login ได้หลายเครื่องพร้อมกัน (ค่าเริ่มต้น)

วิธีเปลี่ยนประเภท Authentication

  1. เปิดไฟล์ Core/AppSettings.cs
  2. เลื่อนไปที่คลาส OAuth
  3. ปรับแก้ 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"
    }
  }
}
  1. AccessTokenExpires คือ อายุ Access Token มีหน่วยเป็นวินาที หากตั้งค่าไว้น้อย จะเพิ่มความปลอดภัย แต่ถ้าหากตั้งไว้มากจะลดภาระเครื่อง Server
  2. RefreshTokenExpires คือ อายุ Refresh Token มีหน่วยเป็นวินาที ใช้ในกรณีที่ต้องการให้ User ทำการ Login ใหม่หากไม่ได้ใช้งานเกิดที่กำหนด
  3. AuthorizationCodeExpires คือ อายุ Authorization Code
  4. Issuer คือ url ที่ให้บริการ OAuth 2.0
  5. SecretKey คือ รหัสลับสำหรับใช้เพื่อสร้าง 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"
    }
  }
}
  1. Host คือ เครื่องที่ให้บริการ LDAP
  2. Port คือ Port ของเครื่องที่ให้บริการ LDAP
  3. SecureSocketLayer คือ ระบุว่าเครื่อง LDAP รองรับ SSL หรือไม่
  4. DistinguishedName (DN) คือ ชื่อของผู้ใช้ที่มีสิทธิเรียกใช้งาน AD โดย username นั้น อย่างน้อยต้องอยู่ในกลุ่มของ Domain Admin ขึ้นไป
  5. AdminUser คือ user ที่เป็น admin
  6. AdminPassword คือ รหัสผ่านของ user ที่เป็น admin
  7. UserIdField คือ key ที่เป็น Field User Id
  8. UsernameField คือ key ที่เป็น Field Username
  9. FirstNameField คือ key ที่เป็น Field ชื่อ
  10. LastNameField คือ key ที่เป็น Field นามสกุล
  11. 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 แบบดังนี้

  1. UserTokenRepository เป็นการเก็บ Token ไว้ที่ Database
services.AddSingleton<IUserTokenRepository, UserTokenRepository>();
  1. UserTokenInMemoryRepository เป็นการเก็บ Token ไว้ที่ Memory เมื่อ Application Pool ถูกรีสตาร์ท ข้อมูลใน Memory จะสูญหาย เหมาะสำหรับแอพที่ไม่มี Database แต่ต้องการมีระบบ Authentication
services.AddSingleton<IUserTokenRepository, UserTokenInMemoryRepository>();

2. IUserInfoRepository

ระบุแหล่งข้อมูลของ User เมื่อมีการ Login ว่าจะให้ตรวจสอบข้อมูล Authentication จากแหล่งไหน โดยการเก็บข้อมูลมี 4 แบบดังนี้

  1. UserInfoRepository ตรวจสอบจากตาราง UM_USER ใน Database
services.AddSingleton<IUserInfoRepository, UserInfoRepository>();
  1. UserInfoLdapRepository ตรวจสอบข้อมูลจาก LDAP
services.AddSingleton<IUserInfoRepository, UserInfoLdapRepository>();
  1. UserInfoFakeRepository ตรวจสอบข้อมูลจากการ Mockup User เหมาะสำหรับแอพที่ไม่มี Database แต่ต้องการมีระบบ Authentication
services.AddSingleton<IUserInfoRepository, UserInfoFakeRepository>();
  1. 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 ด้วยตัวเอง

  1. 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;
}
  1. กำหนดลำดับในการ 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 ไปยังเว็บเซอร์วิสโดยตรง

Password Grant Diagram
  1. User: กรอกชื่อผู้ใช้และรหัสผ่าน – ผู้ใช้จำเป็นต้องกรอกข้อมูลรหัสผู้ใช้และรหัสผ่านผ่านหน้า เข้าสู้ระบบของ Client App โดยตรง
  2. Client App: ร้องขอ Access Token โดยส่งรหัสผู้ใช้และรหัสผ่านไปยังเว็บเซอร์วิส
ParameterRequiredDescription
grant_typetrueระบุเป็น password
usernametrueรหัสผู้ใช้
passwordtrueรหัสผ่านผู้ใช้
client_idfalseควรเป็น 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'
  1. Web Service: ตอบกลับ Access Token หลังจาก Web Service ตอบกลับข้อมูลมาแล้ว Client จะทำการเก็บไว้ที่ Local Storage
KeyDescription
access_tokentoken สำหรับการร้องขอ Resource
token_typeประเภท token
expires_inอายุของ Access Token ในหน่วยวินาที
refresh_tokentoken สำหรับร้องขอ Access Token ใหม่เมื่อหมดอายุ
errorรหัสข้อผิดพลาด
error_descriptionคำอธิบายข้อผิดพลาด

ตัวอย่างการตอบกลับ

  • HTTP Status: 200 OK
{
  "access_token": "eyJhbGciO...",
  "token_type": "bearer",
  "expires_in": 20,
  "refresh_token": "2469d199696..."
}
Access Token
  • HTTP Status: 400 Bad Request
{
  "error": "invalid_grant",
  "error_description": "The username or password is incorrect."
}
  1. Client App: Refresh Token เมื่อ Access Token ปัจจุบันที่ Client App หมดอายุลง
ParameterRequiredDescription
grant_typetrueระบุเป็น refresh_token
refresh_tokentruetoken ที่ใช้สำหรับร้องขอ Access Token ใหม่
client_idfalseควรเป็น 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'
refresh
  1. Web Service: ตอบกลับ Access Token

หลังจาก Web Service ตอบกลับข้อมูลมาแล้ว Client ทำการอัพเดทที่ Local Storage

KeyDescription
access_tokentoken สำหรับการร้องขอ Resource
token_typeประเภท token
expires_inอายุของ Access Token ในหน่วยวินาที
refresh_tokentoken สำหรับร้องขอ 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 CodeError CodeDescription
400unsupported_grant_typeพารามิเตอร์ grant_type ไม่ถูกต้อง หรือ ยังไม่รองรับ
400invalid_grantรหัสผู้ใช้หรือรหัสผ่านไม่ถูกต้อง, Refresh Token หมดอายุหรือไม่ถูกต้อง
400invalid_requestไม่ระบุหรือระบุพารามิเตอร์ไม่ถูกต้อง
401ไม่มีสิทธิ์ในการเข้าถึงข้อมูลหรือ Access Token ไม่ถูกต้อง
Password Grant Diagram
  1. 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>
  1. Redirect with $ : Client App เปิด Web Browser พร้อมกับ Redirect ไปร้องขอ Authorization Code โดยแนบ $ ไปด้วย
ParameterRequiredDescription
response_typetrueระบุเป็น code
redirect_uritrueUri ที่ต้องการให้ Redirect กลับไปที่ App หรือ Web
code_challengetrueผลลัพท์ hash code_verifier
code_challenge_methodtrueระบุเป็น S256
statefalse(แนะนำ) text ที่จะถูกส่งกลับไปให้ Client เมื่อ Redirect กลับไปที่ App หรือ Web เพื่อป้องกันการโจมตีแบบ Cross-site Request Forgery (CSRF)
client_idfalseควรเป็น 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'
  1. Redirect to Login with $ : Web Browser เรียกไปที่ Authorization Server และ Server ทำการเก็บ $ ไว้ จากนั้นจะทำการตรวจสอบว่าผู้ใช้อยู่ในระบบอยู่แล้วหรือไม่ หากไม่อยู่ในระบบจะ Redirect เพื่อไปหน้า Login (ข้อ 4) หากอยู่ในระบบอยู๋แล้ว จะ Redirect พร้อมส่ง Authorization Code กลับไปที่ Web Browser (ข้อ 6)
  2. Returns to Login Form : Authorization Server ทำการ Redirect ไปหน้า Login
  3. Submits Credentials : ผู้ใช้ทำการกรอก Username และ Password และ submit ไปยัง Authorization Server
  4. Redirect with code (a) : Authorization Server ทำการ redirect กลับไป Web Browser พร้อมข้อมูล Authorization Code (a)
  5. 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
  1. Request Token (Authorization Code Exchange)
ParameterRequiredDescription
grant_typetrueระบุเป็น authorization_code
code_verifiertruecode ที่ Client App สร้างไว้ (ข้อ 1)
codetrueAuthorization Code (a)
redirect_uritrueจะต้องเหมือนกับ (ข้อ 2)
client_idfalseควรเป็น 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 ได้เลย