Security

Hiện đại hóa xác thực doanh nghiệp: Tích hợp Azure AD SSO vào hệ thống kế thừa

By Ginbok10 min read

Trong bối cảnh phần mềm doanh nghiệp không ngừng phát triển, quản lý danh tính thường là điểm ma sát lớn nhất cho cả lập trình viên lẫn người dùng cuối. Tại một công ty công nghệ vừa—hãy gọi là TechFlow Solutions—chúng tôi đối mặt với một món nợ kỹ thuật kinh điển: hệ thống quản lý nhân viên nội bộ PeopleHub vẫn đang mắc kẹt ở kỷ nguyên xác thực LDAP (Lightweight Directory Access Protocol).

PeopleHub là xương sống của hoạt động hằng ngày, xử lý mọi thứ từ chấm công, quản lý nghỉ phép đến hồ sơ nhân sự và đánh giá hiệu suất. Mọi nhân viên đều tương tác với nó hằng ngày. Và mỗi ngày, họ phải nhập một tên đăng nhập và mật khẩu hoàn toàn tách biệt với thông tin xác thực Microsoft 365.

Đây là câu chuyện về cách chúng tôi hiện đại hóa xác thực của PeopleHub bằng cách tích hợp Azure Active Directory (Azure AD) Single Sign-On (SSO), loại bỏ tình trạng quá tải mật khẩu và cải thiện đáng kể tình trạng bảo mật.

Vấn đề: Sống chung với LDAP

Nhiều năm qua, PeopleHub xác thực người dùng qua thư mục LDAP nội bộ. Luồng hoạt động rất đơn giản: người dùng nhập thông tin đăng nhập vào form, backend mở LdapConnection, bind với thông tin được cung cấp, và nếu bind thành công thì người dùng được xác thực.

// Cách cũ — LDAP bind authentication
using (var connection = new LdapConnection("corp.techflow.local"))
{
    connection.Bind(new NetworkCredential(username, password, "corp.techflow.local"));
    // Nếu không có exception, thông tin hợp lệ
    var employee = await dbContext.Employees
        .FirstOrDefaultAsync(e => e.UserName == username);
    // Tạo session...
}

Cách này hoạt động, nhưng đi kèm với danh sách vấn đề ngày càng dài:

Tại sao chọn Azure AD SSO?

Quyết định tích hợp Azure AD xuất phát từ một quan sát đơn giản: mọi người tại TechFlow đều đã có tài khoản Azure AD. Microsoft 365 là bộ công cụ năng suất của chúng tôi. Mọi nhân viên đăng nhập máy tính, mở Outlook, tham gia cuộc họp Teams—tất cả qua Azure AD.

Bằng cách tận dụng Azure AD SSO cho PeopleHub, chúng tôi có thể:

Kiến trúc kỹ thuật: Luồng OAuth2 Authorization Code

Azure AD SSO sử dụng luồng OAuth2 Authorization Code, đây là tiêu chuẩn công nghiệp cho ứng dụng web phía server. Đây là luồng hoàn chỉnh chúng tôi đã triển khai:

BROWSER (Vue.js) PEOPLEHUB Backend API AZURE AD OAuth2 / OIDC MS GRAPH API DATABASE Employee DB 1. Click SSO 2. Trả về auth URL (MSAL) 3. Redirect đến trang đăng nhập Azure AD 4. Người dùng đăng nhập (hoặc auto-SSO) 5. Redirect lại với ?code=xxx 6. Chuyển code lên backend 7. Đổi code lấy token 8. Access token 9. GET /me (Bearer token) 10. Thông tin user (email, tên) 11. Tìm nhân viên theo email 12. Bản ghi nhân viên 13. Set cookie + trả về user 14. Redirect đến dashboard ✓

Điểm hay của luồng này là client secret không bao giờ rời khỏi server. Frontend chỉ xử lý redirect và authorization code—nó không bao giờ nhìn thấy token hay secret.

Chi tiết triển khai

1. Đăng ký ứng dụng Azure AD

Trước khi viết bất kỳ dòng code nào, bạn cần đăng ký ứng dụng trong Azure AD:

2. Backend: Tích hợp MSAL

Chúng tôi dùng MSAL (Microsoft Authentication Library) ở backend. MSAL có sẵn cho .NET, Node.js, PythonJava. Đây là cách triển khai .NET:

// Cấu hình
public class AzureAdConfig
{
    public string TenantId { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public string Instance { get; set; } = "https://login.microsoftonline.com/";
    public string[] Scopes { get; set; } = new[] { "User.Read" };
}

// Khởi tạo MSAL confidential client
var app = ConfidentialClientApplicationBuilder
    .Create(config.ClientId)
    .WithAuthority($"{config.Instance}{config.TenantId}")
    .WithClientSecret(config.ClientSecret)
    .Build();

Tạo Login URL

// GET /api/auth/login-url
public async Task<string> GetLoginUrl(string redirectUri)
{
    var authUrl = await _msalClient
        .GetAuthorizationRequestUrl(config.Scopes)
        .WithRedirectUri(redirectUri)
        .ExecuteAsync();
    
    return authUrl.ToString();
}

Đổi Code lấy Token

// GET /api/auth/callback?code=xxx
public async Task<UserSession> HandleCallback(string code, string redirectUri)
{
    // 1. Đổi authorization code lấy token
    var result = await _msalClient
        .AcquireTokenByAuthorizationCode(config.Scopes, code)
        .ExecuteAsync();

    // 2. Gọi MS Graph để lấy profile người dùng
    var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", result.AccessToken);
    
    var graphResponse = await httpClient.GetAsync("https://graph.microsoft.com/v1.0/me");
    var userData = await graphResponse.Content.ReadFromJsonAsync<GraphUser>();

    // 3. Khớp với database nhân viên nội bộ
    var email = userData.Mail ?? userData.UserPrincipalName;
    var username = email.Contains("@") ? email.Split('@')[0] : email;
    
    var employee = await _dbContext.Employees
        .FirstOrDefaultAsync(e => e.UserName == username);

    if (employee == null)
        throw new UnauthorizedException("Không tìm thấy người dùng trong PeopleHub");

    if (!employee.IsActive)
        throw new UnauthorizedException("Tài khoản đã bị vô hiệu hóa");

    // 4. Tạo session (giống cách tiếp cận cookie-based trước đây)
    return new UserSession(employee);
}

3. Frontend: Từ Form đến Button

Thay đổi frontend ít đến bất ngờ. Chúng tôi thay thế toàn bộ form username/password bằng một button duy nhất:

<!-- TRƯỚC: Form đăng nhập truyền thống -->
<form @submit="handleLogin">
  <input v-model="username" placeholder="Tên đăng nhập" />
  <input v-model="password" type="password" placeholder="Mật khẩu" />
  <button type="submit">Đăng nhập</button>
</form>

<!-- SAU: SSO button -->
<button @click="handleSsoLogin" :disabled="isLoading">
  <microsoft-icon />
  Đăng nhập bằng Microsoft
</button>
// auth.service.js
export default {
  getLoginUrl: () => api.get('/api/auth/login-url'),
  handleCallback: (code) => api.get(`/api/auth/callback?code=${code}`),
}

// login.vue
mounted() {
  const code = new URLSearchParams(window.location.search).get('code');
  if (code) {
    this.handleCallback(code);
  }
},
methods: {
  async handleSsoLogin() {
    const { url } = await AuthService.getLoginUrl();
    window.location.href = url;
  },
  async handleCallback(code) {
    const { user } = await AuthService.handleCallback(code);
    this.$store.commit('SET_USER', user);
    this.$router.push('/dashboard');
  }
}

4. Quản lý Session: Không thay đổi

Một trong những quyết định tốt nhất của chúng tôi là giữ nguyên session management dựa trên cookie. Sau khi xác thực Azure AD, backend tạo đúng cookie session như trước:

var claims = new List<Claim>
{
    new Claim("username", employee.UserName),
    new Claim("resourceId", employee.Id.ToString()),
    new Claim("isAdmin", employee.IsAdmin ? "1" : "0"),
};

var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(identity));

Điều này có nghĩa là không có thay đổi nào với authorization middleware, kiểm tra quyền, hay bất kỳ code downstream nào phụ thuộc vào cookie. Toàn bộ codebase hiện tại tiếp tục hoạt động đúng như trước—chỉ có cách thức xác thực thay đổi, không phải kết quả.

Trước & Sau

Khía cạnhTrước (LDAP)Sau (Azure AD SSO)
Giao diện đăng nhậpForm username + passwordButton "Đăng nhập bằng Microsoft"
Quản lý thông tin xác thựcMật khẩu PeopleHub riêngTài khoản Microsoft 365 hiện có
Trải nghiệm SSOKhông có — luôn phải nhậpTự động đăng nhập nếu đã vào Microsoft
MFAKhông hỗ trợKế thừa từ chính sách Azure AD
Reset mật khẩuTicket IT supportTự phục vụ qua Microsoft
Hạ tầngCần LDAP server nội bộCloud-based, không cần VPN
Quản lý sessionCookie-basedCookie-based (không đổi)

Bài học rút ra

1. Chuẩn bị App Registration sớm

Đăng ký ứng dụng Azure AD là điều kiện tiên quyết cho mọi hoạt động phát triển. Bạn cần Client ID, Tenant ID và Client Secret trước khi viết một dòng auth code. Hãy yêu cầu điều này từ Azure AD administrator của bạn ngay từ bước đầu tiên.

2. Không khớp Redirect URI là vấn đề debug #1

Azure AD rất nghiêm ngặt với redirect URIs. URI trong code phải khớp chính xác với những gì đã đăng ký—bao gồm cả dấu gạch chéo cuối, HTTP vs HTTPS, và số port. Chúng tôi đã mất nhiều giờ vì thiếu dấu gạch chéo cuối. Mẹo: ghi log URI redirect chính xác mà code đang tạo ra và so sánh từng ký tự với URI đã đăng ký.

3. Cân nhắc giai đoạn chuyển tiếp

Ban đầu chúng tôi lên kế hoạch chuyển đổi hoàn toàn từ LDAP sang SSO. Thay vào đó, chúng tôi giữ cả hai phương thức đăng nhập trong hai tuần chuyển tiếp. Điều này rất có giá trị để phát hiện edge case và cho người dùng thời gian thích nghi. Form LDAP được ẩn sau link "Dùng đăng nhập cũ" ở cuối trang.

4. Kiểm tra cả người dùng hợp lệ và không hợp lệ

Không phải ai có tài khoản Azure AD cũng là người dùng PeopleHub. Nhà thầu, cộng tác viên bên ngoài, hoặc nhân viên đã rời công ty có thể có quyền truy cập Azure AD nhưng không có bản ghi trong database nội bộ. Hãy đảm bảo thông báo lỗi rõ ràng và có thể thực hiện được.

5. Chú ý đến Scopes

Chỉ yêu cầu các Azure AD scope thực sự cần thiết. User.Read là đủ để lấy email và display name của người dùng. Thêm các scope không cần thiết như Directory.Read.All yêu cầu admin consent và gây ra cờ đỏ khi security review.

6. MSAL xử lý những phần khó

Đừng cố triển khai luồng OAuth2 thủ công bằng raw HTTP calls. MSAL xử lý token caching, refresh token, xác thực authority và các chi tiết protocol. Nó có sẵn cho mọi nền tảng chính và được Microsoft duy trì tích cực.

Kết luận

Tích hợp Azure AD SSO vào PeopleHub là một trong những thay đổi hạ tầng hiếm hoi mà tác động được cảm nhận ngay lập tức bởi mọi người dùng. Ngay ngày deploy, hàng đợi IT support cho reset mật khẩu giảm hơn 40%. Người dùng không còn phàn nàn về "thêm một mật khẩu nữa". Và team bảo mật cuối cùng có sự tự tin rằng MFA được thực thi trên tất cả hệ thống nội bộ.

Nếu tổ chức của bạn dùng Microsoft 365 và vẫn có hệ thống nội bộ với thông tin đăng nhập riêng biệt, tích hợp Azure AD SSO nên nằm gần đầu danh sách nợ kỹ thuật. Luồng OAuth2 Authorization Code được ghi chép đầy đủ, MSAL làm cho việc triển khai trở nên đơn giản, và ROI về trải nghiệm người dùng lẫn bảo mật là rất đáng kể.

Phần khó nhất không phải là code—mà là làm đúng app registration và quản lý redirect URI. Một khi những thứ đó ổn, toàn bộ tích hợp trở nên khá thanh lịch.

#authentication#security#dotnet#architecture#azure
← Back to Articles