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.
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 IDTENANT_ID
with your Tenant Id in Azure ADCOOKIE_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.