1. คำแนะนำเพื่อใช้งานแอปพลิเคชัน ThaIDAuthenExample เพื่อทดสอบการเชื่อมต่อระบบ ThaID ด้วยภาษา C# ASP.NET 8
แอปพลิเคชันเป็นตัวอย่างเพื่อแสดงวิธีการเชื่อมต่อ ThaID โดยใช้ภาษา C# ร่วมกับเฟรมเวิร์ก ASP.NET 8 โดยใช้การยืนยันตัวตนด้วยมาตรฐาน OpenID Connect & OAuth2
โซลูชันนี้มีสองโปรเจกต์
- ThaIDAuthenExample: ใช้ทดสอบการเชื่อมต่อกับ ThaID
- ThaIDAuthenAPIExample: สำหรับจำลองเป็น Authorize Resource เพื่อขอยิง API ข้ามระบบเพื่อทดสอบ
- IdentityModel.OidcClient สำหรับต่อ OAuth2 ของ ThaID
- System.IdentityModel.Tokens.Jwt สำหรับต่อ Validate id token
- .NET 8.0 Runtime (รวมอยู่ในชุดพัฒนาโปรแกรม Visual Studio Community 2022)
- ดาวน์โหลดโปรแกรม Visual Studio Installer สำหรับติดตั้งโปรแกรม Visual Studio Community 2022 โดยกดที่ link download
- เปิดโปรแกรม Visual Studio Installer ให้เลือกโหลด Visual Studio Community 2022 และเลือก ASP.NET and web development กด download และรอจนกว่าการติดตั้งจะเสร็จสมบูรณ์
- เปิดโปรแกรม Visual Studio Community 2022 และเลือก Open a project or solution
- ค้นหาไฟล์โปรเจกต์ชื่อ
ThaIDAuthenExample.slnในโฟลเดอร์ThaID\CSharp\ThaIDAuthenExample - ตั้งค่าสำหรับการเชื่อมต่อ ThaID ตามรายละเอียดดังนี้
location: ThaIDAuthenExample/appsettings.json
แก่ไขค่าในตัวแปรสำหรับการเชื่อมโยงข้อมูล ThaID ได้แก่ client id, client secret, API Key, Callback URL, Scope ตามตัวอย่างในไฟล์ JSON
{
"ThaID": {
"ClientID": "{Client_ID}",
"ClientSecret": "{Client_Secret}",
"APIKey": "{API_Key}",
"RedirectURL": "{Callback_URL}",
"Issuer": "https://imauth.bora.dopa.go.th",
"URLJwks": "https://imauth.bora.dopa.go.th/jwks/",
"Scope": "{Scope}",
"ASEndPoint": "https://localhost:7228"
}
}- เลือกเมนู Startup Item (สัญลักษณ์ ฟันเฟือง⚙️) และเลือก ThaIDAuthenExample สำหรับเปิด Web หรือ ThaIDAuthenAPIExample สำหรับเปิด API
- กดปุ่ม https (สัญลักษณ์ RUN
▶️ สีเขียว)
location: ThaIDAuthenExample/Program.cs
Services สำหรับการจัดการ Session
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30); // Set the session timeout
options.Cookie.HttpOnly = true; // Make the session cookie HTTP-only
options.Cookie.IsEssential = true; // Make the session cookie essential
});Services สำหรับ เชื่อมต่อ ข้อมูลเครื่อง Server อื่น ผ่าน HTTP Rest API ซึ่งในตัวอย่างเป็น Server ThaID
builder.Services.AddHttpClient("DOPA", httpClient =>
{
httpClient.BaseAddress = new Uri("https://imauthsbx.bora.dopa.go.th");
});Services สำหรับ เชื่อมโยง ข้อมูลกับ ThaID
builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();location: ThaIDAuthenExample/Controllers/HomeController.cs
การลงทะเบียน กับ DOPA ค่า Callback URL จะต้องตรงกับค่า Route เพื่อให้ DOPA ส่ง Authorize Code ได้ถูกต้อง ซึ่งสามารถแก้การตั้งค่าได้ที่เว็บ RP Admin
[Route("/authentication/login-callback")]
public async Task<IActionResult> Authentication(string code, string state)
{
TokenResponse tokenResponse = await _authenticationService.RequestTokenAsync(code, state);
CreateSessionToken(tokenResponse);
return View("Authentication",tokenResponse);
}Function สำหรับ หน้าแรก ของเว็บแอปพลิเคชัน
public IActionResult Index()
{
return View();
}Function สำหรับเข้ากระบวนการ ยืนยันตัวตน โดยระบบจะพาผู้ใช้ไป เว็บไซต์ ThaID เพื่อยืนยันตัวตน
[Route("/authentication/login")]
public async Task<IActionResult> login()
{
AuthorizeState provider = await _authenticationService.CreateProvider();
return Redirect(provider.StartUrl);
}Function สำหรับหลังจากเข้ากระบวนการยืนยันตัวตนแล้ว และได้รับ Authorization Code จะเรียก API Request Token เพื่อร้องขอชุด Token มาจาก ThaID
[Route("/authentication/login-callback")]
public async Task<IActionResult> Authentication(string code, string state)
{
TokenResponse tokenResponse = await _authenticationService.RequestTokenAsync(code, state);
CreateSessionToken(tokenResponse);
return View("Authentication",tokenResponse);
}Function สำหรับร้องขอ Token ชุดใหม่ ในกรณี Access Token หมดอายุ หรือใช้งานไม่ได้แล้วโดยเรียกจาก API Refresh Token
[Route("/authentication/RefreshToken")]
public async Task<IActionResult> RefreshToken()
{
var jsonSessionToken = HttpContext.Session.GetString("token");
if (jsonSessionToken != null)
{
var tokenSession = JsonSerializer.Deserialize<TokenResponse>(jsonSessionToken);
TokenResponse tokenResponse = await _authenticationService.RefreshTokenAsync(tokenSession);
CreateSessionToken(tokenResponse);
return View("Authentication", tokenResponse);
}
else
{
return View("Authentication");
}
}Function สำหรับตรวจสอบ ID Token ที่ได้รับจาก ThaID ว่าถูกต้องตามมาตรฐาน OpenID Connect
[Route("/authentication/validateIdToken")]
public async Task<bool> ValidateIdToken()
{
var jsonSessionToken = HttpContext.Session.GetString("token");
if (jsonSessionToken != null)
{
try
{
var tokenSession = JsonSerializer.Deserialize<TokenResponse>(jsonSessionToken);
var resultValidate = await _authenticationService.ValidateIdToken(tokenSession.IDTokenJWT.Header.Kid, tokenSession.IDToken);
return resultValidate;
}
catch (Exception ex)
{
return false;
}
}
else
{
return false;
}
}Function สำหรับการสร้าง Session เพื่อจัดเก็บ Token ให้กับผู้ใช้
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public void CreateSessionToken(TokenResponse tokenForSet)
{
HttpContext.Session.SetString("token", JsonSerializer.Serialize(tokenForSet));
}Function สำหรับการส่งข้อมูลกลับในกรณีมี Error เกิดขึ้น
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}location: ThaIDAuthenExample/Models/TokenModel.cs
ตัวแปร สำหรับการเก็บค่า Token ตามมาตรฐาน OpenID Connect
[JsonPropertyName("access_token")]
public required string AccessToken { get; set; }
[JsonPropertyName("refresh_token")]
public required string RefreshToken { get; set; }
[JsonPropertyName("token_type")]
public required string TokenType { get; set; }
[JsonPropertyName("expires_in")]
public required long ExpiresIn { get; set; }
[JsonPropertyName("scope")]
public required string Scope { get; set; }
[JsonPropertyName("id_token")]
public string? IDToken { get; set; }
public JwtSecurityToken IDTokenJWT
{
get { return ConvertToJWT(IDToken); }
}Function สำหรับการแปลงข้อมูล JWT ที่เป็น String เป็น JWT Data Object เพื่อนำไปใช้งานหรือตรวจสอบความถูกต้อง
private JwtSecurityToken ConvertToJWT(string token)
{
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
return handler.ReadJwtToken(token);
}location: ThaIDAuthenExample/Models/ErrorViewModel.cs
ตัวแปร สำหรับการเก็บค่า Error
namespace ThaIDAuthenExample.Models
{
public class ErrorViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}location: ThaIDAuthenExample/Services/AuthenticationService.cs
ตั้งค่าเริ่มต้นของ Authentication Service และค่าต่าง ๆ ที่จำเป็นต่อการเชื่อมโยงข้อมูลไปยัง ThaID
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly OidcClient _provider;
public AuthenticationService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
var config = new OidcClientOptions
{
Authority = _configuration["ThaID:Issuer"],
ClientId = _configuration["ThaID:ClientID"],
ClientSecret = _configuration["ThaID:ClientSecret"],
RedirectUri = _configuration["ThaID:RedirectURL"],
Scope = _configuration["ThaID:Scope"]
};
_provider = new OidcClient(config);
}Function สำหรับเริ่มกระบวนการยินยันตัวตนด้วย Library IdentityModel.OidcClient
public async Task<AuthorizeState> CreateProvider()
{
var configProvider = _provider.PrepareLoginAsync().Result;
return configProvider;
}Function สำหรับการร้องขอชุด Token หลังจากกระบวนการ ยืนยันตัวตน ของ ThaID
public async Task<TokenResponse> RequestTokenAsync(string code, string state)
{
var configToken = _provider.PrepareLoginAsync().Result;
configToken.StartUrl = $"{_configuration["ThaID:RedirectURL"]}?code={code}&state={state}";
configToken.State = state;
// Get token
var resultToken = await _provider.ProcessResponseAsync(configToken.StartUrl, configToken);
// Convert to TokenResponse model
TokenResponse tokenResponse = JsonSerializer.Deserialize<TokenResponse>(resultToken.TokenResponse.Raw);
return tokenResponse;
}Function สำหรับเริ่มกระบวนการ ยืนยันตัวตน
public async Task<bool> ValidateIdToken(string keyForValidate, string idToken)
{
try
{
var httpClient = new HttpClient();
var jwksResponse = await httpClient.GetAsync(_configuration["ThaID:URLJwks"]);
var jwks = await jwksResponse.Content.ReadAsStringAsync();
var keySet = new JsonWebKeySet(jwks);
var publicKey = keySet.Keys.Where(value => value.KeyId == keyForValidate).FirstOrDefault();
var tokenHandler = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidIssuer = _provider.Options.Authority,
ValidAudience = _provider.Options.ClientId,
IssuerSigningKey = publicKey // Set the public key here
};
var principal = tokenHandler.ValidateToken(idToken, validationParameters, out var validatedToken);
if (principal.Identity.IsAuthenticated)
{
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
return false;
}
}location: ThaIDAuthenAPIExample/appsettings.json
ตัวแปร สำหรับการ ตั้งค่า ที่ใช้กับการเชื่อมโยงข้อมูล ThaID เช่น client id, client secret, API Key เป็นต้น
{
"ThaID": {
"ClientID": "{Client ID from DOPA}",
"ClientSecret": "{Client Secret from DOPA}",
"APIKey": "{API Key from DOPA}"
}
}location: ThaIDAuthenAPIExample/Program.cs
เพิ่ม Services ในการ เชื่อมต่อข้อมูลเครื่อง Server อื่นผ่าน HTTP API
builder.Services.AddHttpClient("DOPA", httpClient =>
{
httpClient.BaseAddress = new Uri("https://imauth.bora.dopa.go.th");
});location: ThaIDAuthenAPIExample/Controllers/TokenInspectController.cs
การตั้งค่า Route สำหรับ Function TokenInspect เพื่อให้ ThaIDAuthenExample ทดสอบว่ายัง ใช้งานได้หรือไม่ ซึ่งใช้ API Introspect Token ในกรณีที่เป็น Authorize Resource และได้รับคำร้องขอข้อมูลมาจากระบบอื่นที่ใช้งาน ThaID
[HttpGet(Name = "TokenInspect")]
public async Task<TokenInspect> Get()
{
return await _authenticationService.TokenIntroSpectAsync(Request.Headers.Authorization);
}location: ThaIDAuthenAPIExample/Controllers/TokenRevokeController.cs
การตั้งค่า Route สำหรับ Function TokeRevoke เพื่อให้ ThaIDAuthenExample ทดสอบยกเลิกการใช้งาน Access Token
[HttpGet(Name = "TokenRevoke")]
public async Task<TokenRevoke> Get()
{
return await _authenticationService.TokenRevokeAsync(Request.Headers.Authorization);
}location: ThaIDAuthenAPIExample/Models/TokenModel.cs
ตัวแปร สำหรับการ เก็บค่า ที่ส่งมาจาก API Inspect Token
public class TokenInspect
{
[JsonPropertyName("active")]
public required bool Active { get; set; }
[JsonPropertyName("sub")]
public string? SubjectIdentifier { get; set; }
[JsonPropertyName("scope")]
public string? Scope { get; set; }
}ตัวแปร สำหรับการ เก็บค่า ที่ส่งมาจาก API Revoke Token
public class TokenRevoke
{
[JsonPropertyName("message")]
public required string Message { get; set; }
}location: ThaIDAuthenAPIExample/Services/AuthenticationService.cs
การตั้งค่าเริ่มต้น Authentication Service และค่าต่าง ๆ ที่จำเป็นต่อ การเชื่อมโยงข้อมูลไปยัง ThaID
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
public AuthenticationService(IHttpClientFactory httpClientFactory, IConfiguration configuration)
{
_httpClientFactory = httpClientFactory;
_configuration = configuration;
}Function สำหรับเริ่มกระบวนการ ยืนยันตัวตน ด้วย Library IdentityModel.OidcClient
public async Task<TokenInspect> TokenIntroSpectAsync(string token)
{
HttpClient httpClient = _httpClientFactory.CreateClient("DOPA");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Authorization,
$"Basic {ClientAuthen(_configuration["ThaID:ClientID"], _configuration["ThaID:ClientSecret"])}"
);
Dictionary<string, string> requestToken = new Dictionary<string, string>
{
{ "token", token }
};
HttpContent httpContent = new FormUrlEncodedContent(requestToken);
HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("api/v2/oauth2/introspect/", httpContent);
httpResponseMessage.EnsureSuccessStatusCode();
string responseStr = await httpResponseMessage.Content.ReadAsStringAsync();
TokenInspect tokenResponse = JsonSerializer.Deserialize<TokenInspect>(responseStr);
return tokenResponse;
}Function สำหรับร้องขอ เพิกถอน Access Token โดยเรียกจาก API Revoke Token
public async Task<TokenRevoke> TokenRevokeAsync(string token)
{
HttpClient httpClient = _httpClientFactory.CreateClient("DOPA");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Authorization,
$"Basic {ClientAuthen(_configuration["ThaID:ClientID"], _configuration["ThaID:ClientSecret"])}"
);
Dictionary<string, string> requestToken = new Dictionary<string, string>
{
{ "token", token }
};
HttpContent httpContent = new FormUrlEncodedContent(requestToken);
HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("api/v2/oauth2/revoke/", httpContent);
httpResponseMessage.EnsureSuccessStatusCode();
string responseStr = await httpResponseMessage.Content.ReadAsStringAsync();
TokenRevoke tokenResponse = JsonSerializer.Deserialize<TokenRevoke>(responseStr);
return tokenResponse;
}Function สำหรับการ สร้าง Authorization Key เพื่อใช้ในการเชื่อมโยงข้อมูลกับ ThaID
private string ClientAuthen(string clientID, string clientSecret)
{
byte[] clientAuthen = System.Text.Encoding.UTF8.GetBytes($"{clientID}:{clientSecret}");
return Convert.ToBase64String(clientAuthen);
}