Implementing Microsoft Login Button with JavaScript and FastAPI

Implementing Microsoft Login Button with JavaScript and FastAPI

This article is based on a recent implementation of Microsoft Auth for one of my projects. See in action here: https://zima.farawaytech.com/

The implementation uses plain JavaScript on the frontend and FastAPI on the backend. It uses modern MSAL library to handle user authentication on the frontend, as the previous library was deprecated some time ago. On the backend, FastAPI verifies the ID token and issues JWT authentication cookie to manage the session.

⚠️
This tutorial skips the section of registering the application in Azure Active Directory. Compared to Google, Microsoft remains notoriously complex for such simple task as implementing a social login. This github repo provides both steps to register app and code samples: https://github.com/Azure-Samples/ms-identity-b2c-javascript-spa. However, it provides a sample for a full user registration workflow through Microsoft, including their custom login popup, where you can add other providers (Google, Facebook). I find this popup barely customizable, so here I focus on a single button that goes directly to Microsoft.

Frontend Setup

Add MSAL javascript library

Include the MSAL JavaScript library in your HTML file by adding the following script tag:

<script src="https://alcdn.msauth.net/browser/2.35.0/js/msal-browser.min.js"></script>

Create a Login Button

Add a login button to your HTML file:

<div class="mt-2">
    <button type="button" id="microsoft-login-button"
            class="w-full py-3 px-4 inline-flex justify-center items-center gap-2 rounded-md border font-medium bg-white text-gray-700 shadow-sm align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-white focus:ring-primary-600 transition-colors text-sm dark:border-gray-700 dark:focus:ring-offset-gray-800">
        <svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21">
            <rect x="1" y="1" width="9" height="9" fill="#f25022"/>
            <rect x="1" y="11" width="9" height="9" fill="#00a4ef"/>
            <rect x="11" y="1" width="9" height="9" fill="#7fba00"/>
            <rect x="11" y="11" width="9" height="9" fill="#ffb900"/>
        </svg>
        Microsoft
    </button>
</div>

Create a JavaScript handler for login

const msalConfig = {
    auth: {
        clientId: "APP_CLIENT_ID",
        authority: "https://login.microsoftonline.com/common"
    },
};

const msalInstance = new msal.PublicClientApplication(msalConfig);

function handleMicrosoftUserSignIn(response) {
    // Handle the user sign-in process here
    const id_token = response.idToken;
    console.log(response.account.username);
    verifyToken("microsoft", id_token);
}

document.getElementById("microsoft-login-button").addEventListener("click", () => {
    const loginRequest = {
        scopes: ["profile", "email"],
    };

    msalInstance
        .loginPopup(loginRequest)
        .then((response) => {
            handleMicrosoftUserSignIn(response);
        })
        .catch((error) => {
            console.error("Error during Microsoft login:", error);
        });
});

Send ID Token to Backend for Verification

Add handler for authentication response from Google: on success it will send the ID token to the FastAPI backend for verification and authentication, and then set the authentication cookie:

function verifyToken(provider, id_token) {
    // Send the ID token to your backend API for verification and authentication
    fetch(`https://your-backend-api.example.com/auth/${provider}`, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({id_token: id_token}),
        credentials: 'include' // Include this line to send and receive cookies
    }).then(response => response.json()).then(data => {
        if (data.status === 'success') {
            // The authentication was successful, and the cookie has been set
            console.log('Authentication successful');
        } else {
            // There was an error during the authentication process
            console.log('Authentication error:', data.message);
        }
    }).catch(error => {
        console.error('Error:', error);
    });
}

Replace https://your-backend-api.example.com with your FastAPI backend's endpoint for handling authentication.

Backend Setup

Install Required Libraries

Install the required libraries for your FastAPI backend:

pip install fastapi[all] pyjwt

Configure CORS in FastAPI

Don't forget to add CORS middleware to your FastAPI application to allow credentials:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8080"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Create an Endpoint for Microsoft Authentication

Create a FastAPI endpoint to handle Microsoft authentication and verify the ID token:

import json
import os
import logging

import httpx
from fastapi import APIRouter

from fastapi import Response
from pydantic import BaseModel
import jwt

app = FastAPI()

MICROSOFT_CLIENT_ID = "YOUR_MICROSOFT_APP_CLIENT_ID"
MICROSOFT_AUTHORITY = "https://login.microsoftonline.com/common"
COOKIE_SECRET = "SECRET_KEY_XXX"


class Token(BaseModel):
    id_token: str


@app.post("/auth/microsoft")
async def authenticate_microsoft_user(token: Token, response: Response):
    id_token = token.id_token
    try:
        decoded_token = jwt.decode(id_token, options={"verify_signature": False})
        tenant_id = decoded_token["tid"]
        user_id = decoded_token["oid"]
        email = decoded_token["email"]

        # Verify the ID token signature
        jwks_url = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
        async with httpx.AsyncClient() as client:
            jwks_resp = await client.get(jwks_url)
            jwks_resp.raise_for_status()
        jwks = jwks_resp.json()
        kid = jwt.get_unverified_header(id_token)["kid"]

        for key in jwks["keys"]:
            if key["kid"] == kid:
                public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
                break
        else:
            raise ValueError("Public key not found in JWKS")

        jwt.decode(id_token, key=public_key, algorithms=["RS256"], audience=MICROSOFT_CLIENT_ID)
        logging.info(f"User {user_id} ({email}) authenticated successfully")

        # Issue an authentication cookie
        auth_cookie = jwt.encode({"user_email": email}, COOKIE_SECRET, algorithm="HS256")
        response.set_cookie(key="auth_cookie", value=auth_cookie)

        return {"status": "success"}

    except jwt.exceptions.InvalidTokenError as e:
        # Invalid ID token
        logging.warning(f"Invalid ID token: {str(e)}")
        return {"status": "error", "message": str(e)}

Replace:

  • YOUR_MICROSOFT_APP_CLIENT_ID string with your actual Application Client ID
  • TENANT_ID with your Tenant Id in Azure AD
  • COOKIE_SECRET_KEY with your own secret key for encoding the JWT authentication cookie.

Microsoft token verification is a bit more complex than Google (see my previous post), as it requires explicitly making an HTTP call. I use asynchronous httpx library here, but you can also use more conventional requests library.

An example of using the cookie in subsequent calls to get some private user data I have also given in my previous post on implementing Google Auth.