Logowanie użytkowników

Przed rozpoczęciem tutaj upewnij się, że rozumiesz, jak zainicjować obiekt aplikacji.

Interfejsy API logowania w bibliotece MSAL pobierają authorization code, które można wymienić na token ID dla zalogowanego użytkownika, przy jednoczesnym wyrażeniu zgody na zakresy dodatkowego zasobu, oraz na token dostępu zawierający zakresy, na które użytkownik wyraził zgodę, aby umożliwić aplikacji bezpieczne wywoływanie interfejsu API.

Wybieranie typu interakcji

Zobacz tutaj , jeśli nie masz pewności co do różnic między loginRedirect i loginPopup.

Logowanie użytkownika

Musisz przekazać obiekt żądania do interfejsów API logowania. Ten obiekt umożliwia używanie różnych parametrów w żądaniu. Zobacz tutaj , aby uzyskać więcej informacji na temat parametrów obiektu żądania.

W przypadku żądań logowania wszystkie parametry są opcjonalne, więc wystarczy wysłać pusty obiekt.

  • Popup
try {
    const loginResponse = await msalInstance.loginPopup({});
} catch (err) {
    // handle error
}
  • Redirect
try {
    msalInstance.loginRedirect({});
} catch (err) {
    // handle error
}

Możesz też wysłać zestaw zakresów uprawnień, aby z góry wyrazić na nie zgodę:

  • Popup
var loginRequest = {
    scopes: ["user.read", "mail.send"] // optional Array<string>
};

try {
    const loginResponse = await msalInstance.loginPopup(loginRequest);
} catch (err) {
    // handle error
}
  • Redirect
var loginRequest = {
    scopes: ["user.read", "mail.send"] // optional Array<string>
};

try {
    msalInstance.loginRedirect(loginRequest);
} catch (err) {
    // handle error
}

Interfejsy API kont

Po pomyślnym wywołaniu logowania można użyć getAllAccounts() funkcji , aby pobrać informacje o aktualnie zalogowanych użytkownikach.

const myAccounts: AccountInfo[] = msalInstance.getAllAccounts();

Jeśli znasz informacje o koncie, możesz również pobrać informacje o koncie przy użyciu interfejsu getAccount() API:

const username = "test@contoso.com";
const myAccount: AccountInfo = msalInstance.getAccount({ username });

const homeAccountId = "userid.hometenantid"; // Best to retrieve the homeAccountId from an account object previously obtained through msal
const myAccount: AccountInfo = msalInstance.getAccount({ homeAccountId });

Note

Filtrowanie według username jest zapewniane dla wygody i powinno być uważane za mniej niezawodne niż wyszukiwanie na homeAccountIdpodstawie . Jeśli to możliwe, użyj polecenia homeAccountId.

W scenariuszach B2C dzierżawa B2C musi być skonfigurowana tak, aby zwracała roszczenie emails w idTokens, aby można było użyć filtru username w interfejsie API getAccount().

Te interfejsy API zwracają obiekt konta lub tablicę obiektów konta z następującym podpisem:

{
    // home account identifier for this account object
    homeAccountId: string;
    // Entity who issued the token represented as a full host of it (e.g. login.microsoftonline.com)
    environment: string;
    // Full tenant or organizational id that this account belongs to
    tenantId: string;
    // preferred_username claim of the id_token that represents this account.
    username: string;
};

Logowanie dyskretne przy użyciu funkcji ssoSilent()

Jeśli masz już sesję, która istnieje z serwerem uwierzytelniania, możesz użyć interfejsu API ssoSilent(), aby wysyłać żądania dotyczące tokenów bez interakcji.

Ze wskazówką użytkownika

Jeśli masz już informacje logowania użytkownika, możesz przekazać je do interfejsu API, aby zwiększyć wydajność i upewnić się, że serwer autoryzacji będzie szukać prawidłowej sesji konta. Aby pomyślnie uzyskać token w trybie dyskretnym, możesz przekazać jeden z poniższych elementów do obiektu żądania.

Zaleca się wykorzystanie opcjonalnegologin_hint oświadczenia tokenu identyfikatora (podanego ssoSilent jako loginHint), ponieważ jest to najbardziej niezawodna wskazówka konta dla dyskretnych (i interaktywnych) żądań.

  • account (które można pobrać za pomocą jednego z interfejsów API konta użytkownika)
  • sid (co można pobrać z idTokenClaims obiektu account)
  • login_hint (można uzyskać na następujące sposoby)
    • Jako właściwość obiektu konta loginHint (zalecane)
    • Jako oświadczenie tokenu identyfikatora login_hint obiektu konta (zalecane)
    • Jako właściwość obiektu konta username (niezalecane)
    • Jako oświadczenie tokenu identyfikatora upn obiektu konta (niezalecane)

Note

Właściwości username i upn są częściowo obsługiwane zamiast faktycznego roszczenia login_hint, ale nie są zalecane. Użyj właściwości konta loginHint lub idTokenClaims.login_hint, jeśli są dostępne.

Po przekazaniu konta system wyszuka opcjonalne roszczenie tokenu login_hint (preferowane), następnie opcjonalne roszczenie tokenu sid, a następnie w ostateczności użyje wartości loginHint (jeśli została podana) lub nazwy użytkownika konta.

const account = msalInstance.getAllAccounts()[0];

const silentRequest = {
    scopes: ["User.Read", "Mail.Read"],
    loginHint: account.loginHint, // alternatively, account.idTokenClaims.login_hint
};

try {
    const loginResponse = await msalInstance.ssoSilent(silentRequest);
} catch (err) {
    if (err instanceof InteractionRequiredAuthError) {
        const loginResponse = await msalInstance.loginPopup(silentRequest).catch(error => {
            // handle error
        });
    } else {
        // handle error
    }
}

Bez wskazówki użytkownika

Jeśli nie ma wystarczających informacji o użytkowniku, możesz spróbować użyć interfejsu ssoSilent API bez przekazywania account, sid ani login_hint.

const silentRequest = {
    scopes: ["User.Read", "Mail.Read"]
};

Należy jednak pamiętać, że jeśli aplikacja ma ścieżki kodu dla wielu użytkowników w jednej sesji przeglądarki lub jeśli użytkownik ma wiele kont dla tej jednej sesji przeglądarki, istnieje większe prawdopodobieństwo błędów logowania dyskretnego. Może zostać wyświetlony następujący błąd w przypadku wielu sesji konta znalezionych przez serwer autoryzacji:

InteractionRequiredAuthError: interaction_required: AADSTS16000: Either multiple user identities are available for the current request or selected account is not supported for the scenario.

Oznacza to, że serwer nie może określić, do którego konta się zalogować, i będzie wymagać jednego z powyższych parametrów (account, login_hint, sid) lub interaktywnego logowania, aby wybrać konto.

Warning

Podczas korzystania z ssoSilent usługa próbuje załadować stronę URI przekierowania w niewidocznym osadzonym elemencie iframe. Zasady bezpieczeństwa treści i wartości nagłówków HTTP obecne w odpowiedzi strony pod adresem URI przekierowania aplikacji, takie jak X-FRAME-OPTIONS: DENY i X-FRAME-OPTIONS: SAMEORIGIN, mogą uniemożliwić załadowanie aplikacji w ramce iframe, co skutecznie blokuje ciche logowanie jednokrotne (SSO). Jeśli zamierzasz użyć ssoSilent, upewnij się, że identyfikator URI przekierowania wskazuje na stronę, która nie stosuje żadnych takich zasad.

Zagadnienia dotyczące identyfikatora RedirectUri

Wszystkie przepływy uwierzytelniania wymagają teraz dedykowanej strony przekierowania, która zawiera implementację mechanizmu przekierowania MSAL. Jest to konieczne, aby zapewnić obsługę nagłówków COOP (Cross-Origin-Opener-Policy) i umożliwić bezpieczną komunikację między oknami wyskakującymi/ramkami iframe a aplikacją główną.

Konfigurowanie strony przekierowania

Twój redirectUri musi wskazywać na dedykowaną stronę, która ładuje skrypt pośredniczący przekierowania. Ta strona powinna:

  1. Załaduj skrypt mostka przekierowania — ten skrypt obsługuje komunikację z głównym oknem
  2. Nie dołączaj żadnego skryptu JavaScript z wyjątkiem skryptu mostka — strona przekierowania powinna uruchamiać tylko skrypt mostka
  3. Nie obejmuje logiki routingu — unikaj bibliotek routerów, które mogą zakłócać obsługę skrótów
  4. Być zarejestrowany w obszarze Rejestracje aplikacji — identyfikator URI musi dokładnie odpowiadać temu, co zarejestrowano w portalu Azure

Przykładowa strona przekierowania (w przypadku korzystania z pakietu, takiego jak Vite lub Webpack):

<!DOCTYPE html>
<html>
<head>
    <title>Redirect</title>
</head>
<body>
    <p>Processing authentication...</p>
    <script type="module">
        import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

        broadcastResponseToMainFrame();
    </script>
</body>
</html>

Note

Specyfikator @azure/msal-browser/redirect-bridge musi być rozpoznawany przez pakiet (Vite, Webpack itp.) — nie jest to adres URL, który przeglądarki mogą pobrać bezpośrednio. Aby uzyskać instrukcje specyficzne dla platformy, zobacz Przewodnik konfigurowania mostka przekierowania.

Konfiguracja

Możesz ustawić element redirectUri globalnie w konfiguracji MSAL lub dla każdego żądania:

Konfiguracja globalna:

const msalConfig = {
    auth: {
        clientId: "your-client-id",
        authority: "https://login.microsoftonline.com/common",
        redirectUri: "http://localhost:3000/redirect"
    }
};

const msalInstance = new PublicClientApplication(msalConfig);

Konfiguracja poszczególnych żądań:

msalInstance.loginPopup({
    scopes: ["user.read"],
    redirectUri: "http://localhost:3000/redirect"
});

Aby uzyskać więcej informacji i pełne przykładowe implementacje, zobacz:

Obsługa błędów wyskakujących okienek interaction_in_progress

W przypadku przepływów w wyskakującym oknie możesz użyć flagi overrideInteractionInProgress, aby anulować oczekującą interakcję i rozpocząć nową. Jest to przydatne w scenariuszach odzyskiwania, w których użytkownik anulował wyskakujące okienko lub interakcję nie powiodła się.

Note

Ta funkcja jest dostępna tylko dla przepływów wyskakujących i nie jest obsługiwana w przypadku przepływów przekierowania. Nagłówek COOP (Cross-Origin-Opener-Policy) zrywa tradycyjne połączenie window.opener, dzięki czemu okna podręczne mogą komunikować się z głównym oknem wyłącznie za pośrednictwem BroadcastChannel.

Ważna

Ustawienie tej opcji na true wymusi anulowanie wszystkich oczekujących żądań uwierzytelniania w wyskakujących oknach, ale nie zamknie żadnych już otwartych wyskakujących okien.

Po ustawieniu wartości na true:

  • Jeśli trwa inna interakcja z okienkiem podręcznym, zostaje ona wymuszenie anulowana, ale żadne otwarte okienka podręczne nie są zamykane
  • Oczekująca interakcja zostaje odrzucona i zwraca błąd interaction_in_progress_cancelled
  • Nowy przepływ wyskakujących okienek jest natychmiast kontynuowany

Prawidłowe przypadki użycia:

  • Odzyskiwanie po błędach polegających na tym, że użytkownik anulował wyskakujące okienko (wyskakujące okienko zostało zamknięte bez ukończenia uwierzytelniania)
  • Implementowanie niestandardowych przepływów odzyskiwania błędów
  • Zapewnienie mechanizmu ponawiania próby po nieudanej interakcji z oknem podręcznym

Domyślny:false

Ważne: używaj tylko po kliknięciu przycisku

Nie ponawiaj próby automatycznie w przypadku przechwycenia błędu interaction_in_progress. Zastąpienie powinno być tylko uruchamiane przez wyraźne działanie użytkownika (na przykład kliknięcie przycisku „Ponów próbę”). Automatyczne zastępowanie interakcji może prowadzić do:

  • Warunki wyścigu między wieloma przepływami uwierzytelniania
  • Nieoczekiwane anulowanie legalnych prób uwierzytelniania
  • Niezadowalające doświadczenie użytkownika z powodu niespodziewanego uruchamiania i przerywania przepływów uwierzytelniania
  • Wiele otwartych okien podręcznych, które nie doprowadzą do pomyślnej odpowiedzi uwierzytelnienia

Przykład: prawidłowa obsługa błędów przy użyciu ponawiania próby wyzwalanej przez użytkownika

Aby zobaczyć kompletne implementacje z wizualną informacją zwrotną, zobacz:

  • Przykład express — demonstruje implementację języka JavaScript z niestandardowym arkuszem CSS
  • Przykład routera React — demonstruje implementację platformy React za pomocą składników Material-UI

Oba przykłady pokazują:

  • Komunikat ostrzegawczy wyświetlany podczas uwierzytelniania w oknie podręcznym
  • Ponów próbę modalne/okno dialogowe z jasnym wyjaśnieniem, gdy interaction_in_progress wystąpi błąd
  • Prawidłowe zarządzanie stanem podczas ponawiania operacji inicjowanego przez użytkownika
  • Składniki interfejsu użytkownika gotowe do produkcji
// State to track if user wants to retry
let userWantsRetry = false;

// Button click handler
async function handleLoginClick() {
    try {
        const loginRequest = {
            scopes: ["user.read"]
        };

        // If user explicitly clicked retry, override the existing interaction
        if (userWantsRetry) {
            loginRequest.overrideInteractionInProgress = true;
            userWantsRetry = false; // Reset flag
        }

        const response = await msalInstance.loginPopup(loginRequest);
        // Handle successful login
    } catch (error) {
        if (error.errorCode === 'interaction_in_progress') {
            // Show retry button to user - DO NOT automatically retry
            showRetryButton();
        } else {
            // Handle other errors
            console.error(error);
        }
    }
}

// Retry button click handler
function handleRetryClick() {
    userWantsRetry = true; // Set flag for next login attempt
    handleLoginClick(); // User explicitly requested retry
}

Przykład: składnik React z ponawianymi próbami wyzwalanymi przez użytkownika

function LoginButton() {
    const { instance } = useMsal();
    const [showRetry, setShowRetry] = useState(false);
    const [retryRequested, setRetryRequested] = useState(false);

    const handleLogin = async () => {
        try {
            const loginRequest = {
                scopes: ["user.read"],
                // Only override if user clicked the retry button
                overrideInteractionInProgress: retryRequested
            };

            setRetryRequested(false); // Reset retry flag

            const response = await instance.loginPopup(loginRequest);
            setShowRetry(false);
        } catch (error) {
            if (error.errorCode === 'interaction_in_progress') {
                // Show retry button - let user decide whether to retry
                setShowRetry(true);
            } else {
                console.error(error);
            }
        }
    };

    const handleRetry = () => {
        setRetryRequested(true); // User explicitly requested retry
        handleLogin();
    };

    return (
        <div>
            <button onClick={handleLogin}>Login</button>
            {showRetry && (
                <button onClick={handleRetry}>
                    Retry Login (Cancel Pending)
                </button>
            )}
        </div>
    );
}

Dalsze kroki

Dowiedz się, jak uzyskać token dostępu i korzystać z niego!