使用 RemixRemix-Auth 的 MicrosoftStrategy

Microsoft 策略用于使用 Remix-AuthMicrosoft Active Directory 上的帐户进行用户身份验证。这可以是工作/学校帐户或个人 Microsoft 帐户,例如 Skype、Xbox 和 Outlook.com。它扩展了 OAuth2Strategy。

支持的运行时

运行时 支持
Node.js
Cloudflare

使用方法

创建 OAuth 应用程序

请按照 Microsoft 文档 中的步骤创建新的应用程序注册。您应该选择 **Web** 作为平台,配置 **重定向 URI** 并添加客户端密钥。

If you want to support login with both personal Microsoft accounts and school/work accounts, you might need to configure the supported account types by editing the manifest file. Set `signInAudience` value to `MicrosoftADandPersonalMicrosoftAccount` to allow login also with personal accounts.

将您的重定向 URI 更改为 https://example.com/auth/microsoft/callbackhttps://127.0.0.1:4200/auth/microsoft/callback(如果您在本地运行)。

确保复制客户端密钥、重定向 URI、租户 ID 和应用程序(客户端)ID(在概述中),因为您稍后将需要它们。

安装依赖项

npm install remix-auth-microsoft remix-auth remix-auth-oauth2

创建策略实例

// app/services/auth.server.ts
import { MicrosoftStrategy } from "remix-auth-microsoft";
import { Authenticator } from "remix-auth";
import { sessionStorage } from "~/services/session.server";

export let authenticator = new Authenticator<User>(sessionStorage); //User is a custom user types you can define as you want

let microsoftStrategy = new MicrosoftStrategy(
  {
    clientId: "YOUR_CLIENT_ID",
    clientSecret: "YOUR_CLIENT_SECRET",
    redirectUri: "https://example.com/auth/microsoft/callback",
    tenantId: "YOUR_TENANT_ID", // optional - necessary for organization without multitenant (see below)
    scope: "openid profile email", // optional
    prompt: "login", // optional
  },
  async ({ accessToken, extraParams, profile }) => {
    // Here you can fetch the user from database or return a user object based on profile
    // return {profile}
    // The returned object is stored in the session storage you are using by the authenticator

    // If you're using cookieSessionStorage, be aware that cookies have a size limit of 4kb
    // For example this won't work
    // return {accessToken, extraParams, profile}

    // Retrieve or create user using id received from userinfo endpoint
    // https://graph.microsoft.com/oidc/userinfo

    // DO NOT USE EMAIL ADDRESS TO IDENTIFY USERS
    // The email address received from Microsoft Entra ID is not validated and can be changed to anything from Azure Portal.
    // If you use the email address to identify users and allow signing in from any tenant (`tenantId` is not set)
    // it opens up a possibility of spoofing users!


    return User.findOrCreate({ id: profile.id });
  }
);

authenticator.use(microsoftStrategy);

有关 scopeprompt 参数的更多信息,请参阅 Microsoft 文档

具有单租户身份验证的应用程序(不允许多租户)

如果您只想允许来自单个组织的用户登录,则应将 tenantId 属性添加到传递给 MicrosoftStrategy 的配置中。tenantId 的值应是您在应用程序注册页面上的 **概述** 中找到的 **目录(租户)ID**。

您还必须在您的应用程序注册中选择 **此组织目录中的帐户** 作为支持的帐户类型。

设置您的路由

// app/routes/login.tsx
export default function Login() {
  return (
    <form action="/auth/microsoft" method="post">
      <button>Login with Microsoft</button>
    </form>
  );
}
// app/routes/auth/microsoft.tsx
import type { ActionArgs } from "@remix-run/node";
import { authenticator } from "~/auth.server";
import { redirect } from "@remix-run/node";

export const loader = () => redirect("/login");

export const action = ({ request }: ActionArgs) => {
  return authenticator.authenticate("microsoft", request);
};
// app/routes/auth/microsoft/callback.tsx
import type { LoaderArgs } from "@remix-run/node";
import { authenticator } from "~/auth.server";

export const loader = ({ request }: LoaderArgs) => {
  return authenticator.authenticate("microsoft", request, {
    successRedirect: "/dashboard",
    failureRedirect: "/login",
  });
};

添加会话存储

// app/services/session.server.ts
import { createCookieSessionStorage } from "@remix-run/node";

export let sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "_session", // use any name you want here
    sameSite: "lax", // this helps with CSRF
    path: "/", // remember to add this so the cookie will work in all routes
    httpOnly: true, // for security reasons, make this cookie http only
    secrets: ["s3cr3t"], // replace this with an actual secret
    secure: process.env.NODE_ENV === "production", // enable this in prod only
  },
});

export let { getSession, commitSession, destroySession } = sessionStorage;