Loading...
Azure-Ad-B2C
Author Cloudapp
E.G.

Einfache Integration von Azure AD B2C in Next.js 14

18. Mai 2024
Inhaltsverzeichnis

Im vorherigen Beitrag habe ich eine Schritt-für-Schritt-Anleitung zur Erstellung eines kostenlosen Azure AD B2C-Tenants gegeben. Wir werden uns auf die Integration dieses Tenants in ein vollwertiges Next.js 14-Projekt konzentrieren. Als Grundlage nehmen wir das Beispiel, das wir in den vergangenen Beiträgen erstellt haben und das auf dem Headless CMS Contentful, der On-Site-Suche Algolia usw. basiert.

Hier ist das GitHub repo mit dem vollständigen Code

Und hier ist ein Spoiler zum Frontend, mit der integrierten Azure AD B2C Auth Solution.

Screenshot-home-login-logout
Screenshot-home-login-logout

Als ersten Schritt rufen wir den API Key von Azure AD B2C ab und verwenden ihn in unserer .env.local-Datei.

Env Variables

NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME=xxxxx -> First Part of the Url xxx.onmicrosoft.com
NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW=B2C_1_Webapp_signupsignin_1
NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI=http://localhost:3000
AZURE_AD_B2C_CLIENT_SECRET=xxxx -> we copied it after the secret creation
NEXT_PUBLIC_AZURE_AD_B2C_CLIENT_ID=xxxx -> Applicaton (client) ID. (point 2 screenshot above)

Dann installieren wir ein neues Paket, "next-auth", Version 4.24.7, welches die Grundlage der Integration bildet. Zusätzlich fügen wir zwei neue Umgebungsvariablen für next-auth hinzu.

NEXTAUTH_SECRET=TBD
NEXTAUTH_URL=http://localhost:3000

Sie können diese Webseite https://www.lastpass.com/features/password-generator für die Generierung eines "Hashes" für die Variable (NEXTAUTH_SECRET) verwenden.

Geänderte und Hinzugefügte Dateien

Hier ist eine Liste aller Dateien, die wir hinzufügen oder ändern werden.

Added Changed Files GIT
Added Changed Files GIT

Nun erstellen wir eine neue Datei unter src/app mit dem Namen providers.tsx.

"use client";

import { SessionProvider } from "next-auth/react";

type Props = {
  children?: React.ReactNode;
};

export const NextAuthProvider = ({ children }: Props) => {
  return <SessionProvider>{children}</SessionProvider>;
};

die wir dann als Wrapper in der layout.tsx-Datei unter src/app/[locale] verwenden. Dies ermöglicht uns, den next-auth-Schutz überall verfügbar zu haben.

return (
    <html lang={htmlLang} suppressHydrationWarning>
      <head></head>
      <body>
        <main className={`${urbanist.variable} font-sans dark:bg-gray-900`}>
          <NextAuthProvider>
            <Providers>
              <Header showBar={true} menuItems={headerdata} logourl={logourl} />
              {draftMode().isEnabled && (
                <p className="bg-emerald-400 py-4 px-[6vw]">
                  Draft mode is on! <ExitDraftModeLink className="underline" />
                </p>
              )}
              {children}
              {/*PiwikPro */}
              <PiwikPro />
              <Footer footerItems={footerdata} />
            </Providers>
          </NextAuthProvider>
        </main>
      </body>
    </html>
  );

Die Hauptdatei, die die Logik verarbeitet, befindet sich unter src/lib und heißt auth.ts. Hier definieren wir Azure AD als Auth Provider und legen die erforderlichen Details für die Integration sowie eine benutzerdefinierte Anmeldeseite fest. Hier können Sie die Umgebungsvariablen sehen, die wir von Azure AD B2C erhalten. Wir definieren die beiden Scopes "offline_access" und "openid" und lesen die "token.role" aus und weisen sie "session.role" zu, sodass wir sie in unserem gesamten Projekt verwenden können.

import type { NextAuthOptions } from "next-auth";
import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";

export const authOptions: NextAuthOptions = {
  pages: {
    signIn: "/", // Custom sign in page
  },
  session: {
    strategy: "jwt",
  },
  providers: [
    AzureADB2CProvider({
      tenantId: process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME,
      clientId: process.env.NEXT_PUBLIC_AZURE_AD_B2C_CLIENT_ID || "",
      clientSecret: process.env.AZURE_AD_B2C_CLIENT_SECRET || "",
      primaryUserFlow: process.env.NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW,
      authorization: {
        params: {
          scope: "offline_access openid",
        },
      },
      checks: ["pkce"],
      client: {
        token_endpoint_auth_method: "none",
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    async signIn({ account, profile }) {
      if (account && profile) {
        return true;
      }
      return true;
    },
    async jwt({ token, account, user, profile }) {
      if (account) {
        token.accessToken = account.id_token;
      }
      if (user) {
        // token.role = user.role;
        // token.subscribed = user.subscribed;
      }
      if (profile) {
        token.role = profile?.extension_Rolle;
      }
      return token;
    },
    async session({ session, token, user }) {
      session.accessToken = token.accessToken;
      session.role = token.role;
      session.user.sub = token.sub;

      return session;
    },
  },
};

Mit der Hauptkonfiguration an Ort und Stelle können wir die benötigte API-Route unter src/app/api/auth/[..nextauth]/route.ts erstellen.

import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Da das Projekt auf TypeScript basiert, müssen wir auch eine Datei für die verwendeten Typen bereitstellen. Erstellen wir also eine unter src/types/next-auth.d.ts.

import NextAuth, { DefaultSession } from "next-auth";

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    accessToken?: string;
    role?: string;
    user: {
      name: string;
      email: string;
      sub: string | undefined;
    };
  }

  interface Profile {
    extension_Rolle?: string;
    emails?: string;
    name?: string;
  }
}

declare module "next-auth/jwt" {
  /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
  interface JWT {
    /** OpenID ID Token */
    accessToken?: string;
    role?: string;
  }
}

Die Logik ist jetzt fertig, daher werden wir unsere Header-Komponente anpassen, da wir ein neues Icon für die Anmeldung/Registrierung und Abmeldung anzeigen müssen. Dazu passen wir die navbar.component.tsx unter src/components/header an. Unten ist die vollständige Datei.

"use client";

import { Disclosure, Menu, Transition } from "@headlessui/react";
import { Bars3Icon, BellIcon, XMarkIcon } from "@heroicons/react/24/outline";
import DarkModeButton from "@/components/header/darkmode.component";
import Image from "next/image";
import Link from "next/link";
import { Fragment } from "react";
// Authentication
import { signIn, useSession } from "next-auth/react";
// Internationalization
import { useTranslation } from "@/app/i18n/client";
import type { LocaleTypes } from "@/app/i18n/settings";
import {
  useRouter,
  usePathname,
  useParams,
  useSelectedLayoutSegments,
  useSearchParams,
} from "next/navigation";
import {
  GlobeAmericasIcon,
  UserCircleIcon,
  UsersIcon,
} from "@heroicons/react/24/solid";

function classNames(...classes: any[]) {
  return classes.filter(Boolean).join(" ");
}

interface Linkitems {
  key: number;
  name: string;
  href: string;
}

export default function Navbar({ menuItems, logourl }: any) {
  // Internationalization
  const locale = useParams()?.locale as LocaleTypes;
  const pathname = usePathname();
  const currentRoute = pathname;
  const { t } = useTranslation(locale, "common");

  // Authentication
  const SIGN_OUT_URL = `https://${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.b2clogin.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.onmicrosoft.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW}/oauth2/v2.0/logout?post_logout_redirect_uri=${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}/${locale}`;

  const { data: session } = useSession();
  const user = session?.user;
  const role = session?.role;

  const { push } = useRouter();
  const router = useRouter();
  const urlSegments = useSelectedLayoutSegments();

  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get("callbackUrl") || `/${locale}/`;

  async function handleLocaleChange(event: any) {
    const newLocale = event;

    // This is used by the Header component which is used in `app/[locale]/layout.tsx` file,
    // urlSegments will contain the segments after the locale.
    // We replace the URL with the new locale and the rest of the segments.
    router.push(`/${newLocale}/${urlSegments.join("/")}`);
  }

  async function logoutredirect() {
    const logoutfeedback = await fetch(
      "/api/auth/signout?callbackUrl=/api/auth/session",
      {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: await fetch("/api/auth/csrf").then((rs) => rs.text()),
      }
    );

    // console.log(logoutfeedback);
    // console.log(SIGN_OUT_URL);
    push(SIGN_OUT_URL);
  }

  function NavigationLink({ href, name }: Linkitems) {
    return (
      <a
        href={href}
        className={
          currentRoute === href
            ? "border-indigo-500 text-gray-900 dark:text-indigo-500 inline-flex items-center px-1 pt-1 border-b-2 text-base font-medium"
            : "border-transparent text-gray-500 dark:text-gray-50 hover:border-gray-300 hover:text-gray-700 inline-flex items-center text-base px-1 pt-1 border-b-2 font-medium"
        }
      >
        {name}
      </a>
    );
  }

  function NavigationLinkDisclosure({ href, name }: Linkitems) {
    return (
      <Disclosure.Button
        as="a"
        href={href}
        className={
          currentRoute === href
            ? "bg-indigo-50 border-indigo-500 text-indigo-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
            : "border-transparent text-gray-600 dark:text-gray-100 hover:bg-gray-100 dark:hover:text-indigo-700 hover:border-gray-300 hover:text-gray-800 block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
        }
      >
        {name}
      </Disclosure.Button>
    );
  }

  return (
    <Disclosure
      as="nav"
      className="px-2 py-2.5 dark:border-gray-700 dark:bg-gray-900 sm:px-4"
    >
      {({ open }) => (
        <>
          <div className="flex flex-wrap items-center justify-between mx-auto">
            <Link className="flex items-center" href={`/${locale}/`}>
              <Image
                className="block float-left w-auto h-12 lg:hidden dark:bg-blue-100"
                src={logourl ? logourl : "/images/svgrepo-com.svg"}
                alt="Testblog"
                width={48}
                height={48}
              />

              <Image
                className="hidden float-left w-auto h-12 lg:block dark:bg-blue-100"
                src={logourl ? logourl : "/images/svgrepo-com.svg"}
                alt="Testblog"
                width={48}
                height={48}
              />
            </Link>

            <div className="hidden lg:ml-6 lg:flex lg:space-x-8">
              {/* Desktop View */}
              {menuItems.map((menuItem: any, index: number) => (
                <NavigationLink
                  key={index}
                  // href={menuItem.href}
                  href={`/${locale}${menuItem.href}`}
                  name={menuItem.name}
                />
              ))}
            </div>

            <div className="flex items-center justify-center flex-1 px-2 lg:ml-6 lg:justify-end">
              <DarkModeButton />

              {/* Language dropdown */}
              <Menu as="div" className="relative flex-shrink-0 ml-4">
                <div>
                  <Menu.Button className="flex text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                    <span className="sr-only">
                      {" "}
                      {t("user.languageswitcher")}
                    </span>
                    <GlobeAmericasIcon
                      className="w-8 h-8 hover:text-gray-500"
                      aria-hidden="true"
                    />
                  </Menu.Button>
                </div>
                <Transition
                  as={Fragment}
                  enter="transition ease-out duration-100"
                  enterFrom="transform opacity-0 scale-95"
                  enterTo="transform opacity-100 scale-100"
                  leave="transition ease-in duration-75"
                  leaveFrom="transform opacity-100 scale-100"
                  leaveTo="transform opacity-0 scale-95"
                >
                  <Menu.Items className="absolute right-0 z-10 block w-48 py-1 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
                    <Menu.Item>
                      {({ active }) => (
                        <a
                          onClick={() => {
                            handleLocaleChange("en-US");
                          }}
                          className={classNames(
                            active ? "bg-gray-100" : "",
                            "block px-4 py-2 text-sm text-gray-700"
                          )}
                        >
                          🇺🇸 {t("languages.en")}
                        </a>
                      )}
                    </Menu.Item>
                    <Menu.Item>
                      {({ active }) => (
                        <a
                          onClick={() => {
                            handleLocaleChange("de-DE");
                          }}
                          className={classNames(
                            active ? "bg-gray-100" : "",
                            "block px-4 py-2 text-sm text-gray-700"
                          )}
                        >
                          🇩🇪 {t("languages.de")}
                        </a>
                      )}
                    </Menu.Item>
                  </Menu.Items>
                </Transition>
              </Menu>
              {/* Profile dropdown */}
              <Menu as="div" className="relative flex-shrink-0 ml-4">
                <div>
                  <Menu.Button className="flex text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                    <span className="sr-only">Open user menu</span>
                    {(!user && (
                      <UserCircleIcon
                        className="w-8 h-8 hover:text-gray-500"
                        aria-hidden="true"
                      />
                    )) ||
                      (user && (
                        <UsersIcon
                          className="w-8 h-8 p-1 hover:text-gray-500"
                          aria-hidden="true"
                        />
                      ))}
                  </Menu.Button>
                </div>
                <Transition
                  as={Fragment}
                  enter="transition ease-out duration-100"
                  enterFrom="transform opacity-0 scale-95"
                  enterTo="transform opacity-100 scale-100"
                  leave="transition ease-in duration-75"
                  leaveFrom="transform opacity-100 scale-100"
                  leaveTo="transform opacity-0 scale-95"
                >
                  <Menu.Items className="absolute right-0 z-10 block w-48 py-1 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
                    {user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            href={`/${locale}/profile`}
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.profile")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                    {user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            onClick={() => {
                              logoutredirect();
                            }}
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.logout")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                    {!user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            onClick={() =>
                              signIn("azure-ad-b2c", { callbackUrl })
                            }
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.login")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                  </Menu.Items>
                </Transition>
              </Menu>
            </div>

            <div className="flex items-center lg:hidden">
              {/* Mobile menu button */}
              <Disclosure.Button className="inline-flex items-center justify-center p-2 text-gray-400 rounded-md hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
                <span className="sr-only">Open main menu</span>
                {open ? (
                  <XMarkIcon className="block w-6 h-6" aria-hidden="true" />
                ) : (
                  <Bars3Icon className="block w-6 h-6" aria-hidden="true" />
                )}
              </Disclosure.Button>
            </div>
          </div>

          <Disclosure.Panel className="lg:hidden">
            <div className="pt-2 pb-3 space-y-1">
              {/* Only show up in Mobile view */}
              {menuItems.map((menuItem: any, index: number) => (
                <NavigationLinkDisclosure
                  key={index}
                  // href={menuItem.href}
                  href={`/${locale}${menuItem.href}`}
                  name={menuItem.name}
                />
              ))}
            </div>
          </Disclosure.Panel>
        </>
      )}
    </Disclosure>
  );
}

Was hat sich seit dem letzten Beitrag geändert, in dem wir die Contentful-Tags zu unserem Projekt hinzugefügt haben? Da wir “useSearchParams” als Import hinzugefügt haben, wird Next.js 14 während des Build-Prozesses eine Fehlermeldung präsentieren.

useSearchParams() should be wrapped in a suspense boundary

daher müssen wir die Navbar-Komponente in der Header-Komponente in eine Suspense Boundary einfügen.

// Header Component
import Navbar from "@/components/header/navbar.component";
import SearchBar from "./search.component";
import { Suspense } from "react";

interface HeaderProps {
  showBar: boolean;
  menuItems: any;
  logourl: string;
}

export default function Header({ showBar, menuItems, logourl }: HeaderProps) {
  return (
    <header>
      {/* <DarkModeButton /> */}
      {/* <NavbarBanner /> */}
      <Suspense fallback={<div>Loading...</div>}>
        <Navbar menuItems={menuItems} logourl={logourl} />
      </Suspense>
      {showBar && (
        <SearchBar
          searchCta="Search"
          searchPlaceholder="Search example.dev..."
        />
      )}
    </header>
  );
}
// New Imports
import { signIn, useSession } from "next-auth/react";
import { useSearchParams} from "next/navigation";

// New Icons
import {
  GlobeAmericasIcon,
  UserCircleIcon,
  UsersIcon,
} from "@heroicons/react/24/solid";

// New variables
  const SIGN_OUT_URL = `https://${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.b2clogin.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.onmicrosoft.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW}/oauth2/v2.0/logout?post_logout_redirect_uri=${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}/${locale}`;

  const { data: session } = useSession();
  const user = session?.user;
  const role = session?.role;

  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get("callbackUrl") || `/${locale}/`;

// New Logoutredirect Async Function
  async function logoutredirect() {
    const logoutfeedback = await fetch(
      "/api/auth/signout?callbackUrl=/api/auth/session",
      {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: await fetch("/api/auth/csrf").then((rs) => rs.text()),
      }
    );

    // console.log(logoutfeedback);
    // console.log(SIGN_OUT_URL);
    push(SIGN_OUT_URL);
  }

// New Menuitem
 {/* Profile dropdown */}
              <Menu as="div" className="relative flex-shrink-0 ml-4">
                <div>
                  <Menu.Button className="flex text-sm bg-white rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                    <span className="sr-only">Open user menu</span>
                    {(!user && (
                      <UserCircleIcon
                        className="w-8 h-8 hover:text-gray-500"
                        aria-hidden="true"
                      />
                    )) ||
                      (user && (
                        <UsersIcon
                          className="w-8 h-8 p-1 hover:text-gray-500"
                          aria-hidden="true"
                        />
                      ))}
                  </Menu.Button>
                </div>
                <Transition
                  as={Fragment}
                  enter="transition ease-out duration-100"
                  enterFrom="transform opacity-0 scale-95"
                  enterTo="transform opacity-100 scale-100"
                  leave="transition ease-in duration-75"
                  leaveFrom="transform opacity-100 scale-100"
                  leaveTo="transform opacity-0 scale-95"
                >
                  <Menu.Items className="absolute right-0 z-10 block w-48 py-1 mt-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
                    {user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            href={`/${locale}/profile`}
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.profile")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                    {user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            onClick={() => {
                              logoutredirect();
                            }}
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.logout")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                    {!user && (
                      <Menu.Item>
                        {({ active }) => (
                          <a
                            onClick={() =>
                              signIn("azure-ad-b2c", { callbackUrl })
                            }
                            className={classNames(
                              active ? "bg-gray-100" : "",
                              "block px-4 py-2 text-sm text-gray-700"
                            )}
                          >
                            {t("user.login")}
                          </a>
                        )}
                      </Menu.Item>
                    )}
                  </Menu.Items>
                </Transition>
              </Menu>

Da wir im Dropdown-Menü auf eine Profilseite verlinken, müssen wir diese unter src/app/[locale]/profile/page.tsx erstellen.

export const dynamic = "force-dynamic";

import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { createTranslation } from "@/app/i18n/server";
import { LocaleTypes } from "@/app/i18n/settings";

interface PageParams {
  slug: string;
  locale: string;
}

interface PageProps {
  params: PageParams;
}

export default async function Profile({ params }: PageProps) {
  const session = await getServerSession(authOptions);
  const user = session?.user;
  const { t } = await createTranslation(params.locale as LocaleTypes, "common");

  return (
    <>
      <section className="min-h-screen pt-20 bg-ct-blue-600">
        <div className="max-w-4xl mx-auto bg-ct-dark-100 rounded-md h-[20rem] flex justify-center items-center">
          <div>
            <h1 className="mb-3 text-5xl font-semibold text-center">
              {t("pages.profile")}
            </h1>
            {!user ? (
              <p>Loading...</p>
            ) : (
              <div className="flex items-center gap-8">
                <div></div>
                <div className="mt-8">
                  <p className="mb-3">Name: {user.name}</p>
                  <p className="mb-3">Email: {user.email}</p>
                  <p className="mb-3">Rolle: {session?.role}</p>
                </div>
              </div>
            )}
          </div>
        </div>
      </section>
    </>
  );
}

Da wir neue Labels eingeführt haben, müssen wir auch die Übersetzungsdatei (common.json) für jede Sprache unter src/app/i18n/locales/... anpassen. Das war's. Jetzt haben wir die grundlegende Integration, und Sie können sie bereits in Ihrer lokalen Umgebung testen mit

npm run dev

Im vorherigen Beitrag haben wir den Azure AD B2C-Mandanten erstellt, indem wir nur eine App-Registrierung, „Localhost“, erstellt haben. Für die Live-Bereitstellung müssen wir eine zweite App-Registrierung erstellen, die für die Vercel-Bereitstellung verwendet wird.

Name der App Registrierung (wir verwenden “TestProd”)

TestProd-AD-Overview
TestProd-AD-Overview

Auswahl “Authentication” -> Add a platform -> Single Page Application

TestProd-AD-Auth
TestProd-AD-Auth

Zertifikate & Geheimnisse -> Geheimnis hinzufügen (Client Secret)

TestProd-AD-Certificate
TestProd-AD-Certificate

API Berechtigungen (offline_access and openid)

Wird bei der Erstellung der App-Registrierung zugewiesen

TestProd-AD-Rights
TestProd-AD-Rights

Nach der Bereitstellung haben Sie ein voll funktionsfähiges Authentifizierungssystem basierend auf Azure AD B2C - und das kostenlos. Klicken Sie auf das Benutzer-Icon oben rechts, damit das Dropdown-Menü geöffnet wird. Klicken Sie nun auf „Login/Register“.

Homepage Login Dropdown
Homepage Login Dropdown

Klicken Sie nun auf „Login/Register“, um die von Azure bereitgestellte Seite zu öffnen.

Sign-up-or-sign-in
Sign-up-or-sign-in

Sobald Sie eingeloggt sind, wird im Dropdown-Menü (oben rechts) ein zweiter Menüpunkt mit dem Namen "Profil" angezeigt. Klicken Sie darauf, um die Details Ihrer Registrierung zu sehen. Genießen Sie es, verwenden Sie es für Ihr Produktionssystem oder spielen Sie damit herum, um es besser zu verstehen. Wenn Sie Hilfe benötigen oder während der Bereitstellung auf Probleme stoßen, lassen Sie es mich bitte in den Kommentaren wissen, damit ich Ihnen helfen kann. Mit diesem vollständigen Beispiel haben wir die Grundlagen implementiert, und in den kommenden Beiträgen werden wir es nutzen, um Authentifizierungsabläufe zu integrieren, wo wir sie benötigen. Bleiben Sie dran.

Cloudapp-dev und bevor Sie uns verlassen

Danke, dass Sie bis zum Ende gelesen haben. Noch eine Bitte bevor Sie gehen:

  • Wenn Ihnen gefallen hat was Sie gelesen haben oder wenn es Ihnen sogar geholfen hat, dann würden wir uns über einen "Clap" 👏 oder einen neuen Follower auf unseren Medium Account sehr freuen.

Verwandte Artikel