0

Phần 2: Authentication Trong Micro Frontend Với Nx Workspace - "Dễ Như Ăn Kẹo"

Chào mừng bạn trở lại với series "Micro Frontend Với Nx Workspace"! Sau phần 1 về setup cơ bản, hôm nay chúng ta sẽ cùng "ăn gà" với chủ đề Authentication - bài toán khiến nhiều dev "đau đầu" khi làm micro frontend.

I. "Bi kịch" của dev khi làm auth với Micro Frontend 😅

Bạn đã bao giờ gặp phải những tình huống "dở khóc dở cười" này chưa?

  • "Đăng nhập cả ngày không hết": User đăng nhập app A xong, qua app B lại phải đăng nhập tiếp. Cứ như đi làm mà phải trình thẻ 5 lần vậy!

  • "Token như ma": Biến mất sau mỗi lần refresh, để rồi user phải đăng nhập lại. Chắc token sợ bị hack nên tự động ẩn mình?

  • "Config như đàn gà con": Mỗi micro app một config auth y chang nhau. Sửa một chỗ phải sửa 10 chỗ, đúng kiểu "sửa lỗi này sinh lỗi kia"!

Đừng lo! Hôm nay mình sẽ cùng bạn "trị" mấy con bug khó ưa này một cách "ngon lành" nhất!

II. 3 Cách "đối phó" với Auth - Chọn cách nào đây? 🤔

Kiểu "Mạnh ai nấy chạy" (Anti-Pattern)

<Auth0Provider domain="..." clientId="...">
  <App />
</Auth0Provider>

🔹 Ưu điểm:

  • Code nhanh như "mì ăn liền"
  • Không phụ thuộc vào ai cả

💥 Nhược điểm:

  • User phải đăng nhập nhiều lần (user chửi thề)
  • Token "biến mất" khi chuyển app
  • Sửa 1 chỗ = sửa 10 chỗ (đau đầu x3)

Kiểu "Chuyền điện thoại" (Event-based)

// Host app phát event
window.dispatchEvent(new CustomEvent('auth-update', {
  detail: { user, isAuthenticated }
}));

// Host app
<Auth0Provider {...config}>
  <MicroAppLoader>
    <Dashboard auth={authProps} />
  </MicroAppLoader>
</Auth0Provider>

// Micro app nhận
window.addEventListener('auth-update', (e) => {
  updateAuthState(e.detail);
});

🔹 Ưu điểm:

  • Nhẹ như lông hồng
  • Linh hoạt với mọi framework

💥 Nhược điểm:

  • Khó debug khi có bug
  • Dễ thành "ma trận" events

Kiểu "Ông trùm lo hết" (Host Provider) - RECOMMENDED! 🎯

// Host app
<Auth0Provider {...config}>
  <MicroAppLoader>
    <Dashboard auth={authProps} />
  </MicroAppLoader>
</Auth0Provider>

🔹 Ưu điểm:

  • Single Sign-On mượt như silk
  • Token refresh tập trung
  • Config 1 chỗ, dùng mọi nơi

💥 Nhược điểm:

  • Cần thiết kế kỹ từ đầu
  • Phụ thuộc vào host app

👉 Kết luận: Nếu muốn ngủ ngon, hãy chọn cách 3! 😴

III. "Chân kinh" triển khai với Auth0 + Nx 🚀

Bước 1: Setup Auth0 Trên Dashboard

  • Vào Auth0 Dashboard

  • Chọn SPA Application và React vì host app đang viết bằng nó. Lúc này bạn sẽ thấy màn hình sẽ như này Screenshot 2025-03-30 at 15.35.41.png

  • Tiếp tục chọn Continue đến khi bạn thấy màn hình này: Screenshot 2025-03-30 at 15.37.51.png

  • Nhấn vào nút Create Application để tạo ứng dụng mới hoặc vào mục Applications bên sidebar.

  • Cấu hình Applications/Settings

Allowed Callback URLs:
  - http://localhost:4200

Allowed Logout URLs:
  - http://localhost:4200

Allowed Web Origins:
  - http://localhost:4200

Advanced Settings:
  - Refresh Token Rotation: Enabled
  • Tạo API Audience (nếu cần gọi backend API):

    • Vào "Applications" → "APIs" → "Create API"

    • Đặt tên và identifier (ví dụ: https://api.your-app.com)

    • Chọn signing algorithm là RS256

  • Làm theo phần Quick Setup bạn sẽ có đc Auth Config. Lưu nó vào env file

NX_AUTH0_DOMAIN=your-tenant.auth0.com
NX_AUTH0_CLIENT_ID=your-client-id-from-auth0-dashboard
NX_AUTH0_AUDIENCE=https://api.your-app.com  # Nếu dùng API

Bước 2: Tạo Auth Library trong Nx

  • Cài đặt thư viện
# Thêm Auth0 React SDK vào host app
nx add @auth0/auth0-react --project=host

# Thêm thư viện types (nếu dùng TypeScript)
npm install --save-dev @types/auth0__auth0-react

nx generate @nx/react:library auth --directory=libs/shared --importPath=@your-org/shared-auth

Cấu trúc thư viện:

libs/shared/auth/
├── src/
│   ├── lib/
│   │   ├── auth-context.tsx
│   │   ├── auth-guard.tsx
│   │   └── use-auth.ts
│   └── index.ts

Bước 3: Triển khai Auth Provide

// libs/shared/auth/src/lib/auth-context.tsx
import { createContext, useContext } from 'react';
import { useAuth0, Auth0ContextInterface } from '@auth0/auth0-react';

type AuthContextType = Auth0ContextInterface;

const AuthContext = createContext<AuthContextType | null>(null);

export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
  const auth = useAuth0();
  return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

Bước 4: Sử dụng trong Host App

// apps/host/src/app/app.tsx
import { Auth0Provider } from '@auth0/auth0-react';
import { AuthProvider } from '@your-org/shared-auth';

function App() {
  return (
    <Auth0Provider
      domain={import.meta.env.VITE_AUTH0_DOMAIN}
      clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
      authorizationParams={{
        redirect_uri: window.location.origin,
        audience: import.meta.env.VITE_AUTH0_AUDIENCE,
      }}
      cacheLocation="localstorage"
      useRefreshTokens
    >
      <AuthProvider>
        {/* App layout và routes */}
      </AuthProvider>
    </Auth0Provider>
  );
}

Bước 5: Sử dụng trong Micro Apps

// apps/micro-app/src/components/user-profile.tsx
import { useAuth } from '@your-org/shared-auth';

function UserProfile() {
  const { user, isAuthenticated, loginWithRedirect } = useAuth();
  
  if (!isAuthenticated) {
    return <button onClick={() => loginWithRedirect()}>Login</button>;
  }

  return <div>Hello, {user.name}</div>;
}

IV. Best Practices cho Production

Token Auto-Refresh

Thêm vào AuthService class:

// libs/shared/auth/src/lib/auth-context.tsx
useEffect(() => {
  const interval = setInterval(async () => {
    try {
      await getAccessTokenSilently({
        cacheMode: 'off',
        detailedResponse: true,
      });
    } catch (error) {
      console.error('Token refresh failed:', error);
      clearInterval(interval);
    }
  }, 5 * 60 * 1000); // 5 phút

  return () => clearInterval(interval);
}, [getAccessTokenSilently]);

Route Guarding

  • Tạo component libs/auth/src/lib/AuthGuard.tsx:
// libs/shared/auth/src/lib/auth-guard.tsx
export const AuthGuard = ({ children }: { children: React.ReactNode }) => {
  const { isAuthenticated, isLoading, loginWithRedirect } = useAuth();

  useEffect(() => {
    if (!isLoading && !isAuthenticated) {
      loginWithRedirect({
        appState: { returnTo: window.location.pathname },
      });
    }
  }, [isLoading, isAuthenticated]);

  if (isLoading) {
    return <LoadingSpinner />;
  }

  return isAuthenticated ? children : null;
};

// Sử dụng trong routes
<Route
  path="/dashboard"
  element={
    <AuthGuard>
      <DashboardPage />
    </AuthGuard>
  }
/>

Error Handling

// libs/shared/auth/src/lib/error-boundary.tsx
export const AuthErrorBoundary = ({ children }: { children: React.ReactNode }) => {
  const { error } = useAuth();

  if (error) {
    return (
      <div>
        <h1>Authentication Error</h1>
        <p>{error.message}</p>
        <button onClick={() => window.location.reload()}>Retry</button>
      </div>
    );
  }

  return children;
};

IV. Kết luận - "Đánh bại" auth khó nhằn! 🏆

Giờ thì bạn đã có đủ "vũ khí" để:

✅ Triển khai auth tập trung

✅ Xử lý token "cứng"

✅ Cho user trải nghiệm mượt mà

Nhớ nhé:

"Làm auth mà không dùng shared library giống như đi đánh nhau mà quên mang vũ khí!" 😂

Chúc bạn triển khai auth thành công và... ít bug nhất có thể! 🐧


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí