OpenID Connect (OIDC) adds authentication on top of OAuth 2.0. With Microsoft Entra ID, you can securely authenticate applications and issue tokens that verify identity.
In this guide, we’ll cover:
-
What OIDC is and how it works.
-
Step-by-step setup in the Microsoft Entra portal.
-
A two–Service Principal architecture.
-
Code samples in Python, Java, and .NET.
-
How to validate tokens.
1. What is OIDC and How Does it Work?
OpenID Connect (OIDC) is an identity layer built on top of the OAuth 2.0 protocol.
-
OAuth 2.0 is mainly about authorization → “Can this app access my data?”
-
OIDC adds authentication → “Who is this user or service really?”
In other words:
-
OAuth says what you can do.
-
OIDC says who you are.
OIDC introduces a new kind of token called the ID token.
-
An access token is for APIs (what you can access).
-
An ID token is for the application (who you are).
The ID token is a JWT (JSON Web Token) that contains claims such as:
-
sub
→ a unique identifier for the user -
name
,email
→ basic profile details -
aud
→ the app the token is meant for -
iss
→ the issuing authority (Microsoft Entra ID) -
exp
→ expiration time

🔄 How the OIDC Flow Works
-
App Registration
-
The app is registered in Microsoft Entra ID so it can request tokens.
-
-
Discovery
-
The app fetches Entra’s discovery document (
/.well-known/openid-configuration
) to get endpoints and public keys.
-
-
Authentication Request
-
The app redirects the user (or service) to the /authorize endpoint with parameters like
client_id
,scope=openid
,redirect_uri
, and anonce
.
-
-
User Sign-In
-
The user enters their credentials, MFA, or uses single sign-on (SSO).
-
-
Token Issuance
-
Entra ID issues an ID token (plus optionally an access token).
-
-
Token Validation
-
The application validates the ID token’s signature and claims.
-
-
Session Established
-
The app trusts the token and authenticates the user.
-
2. Step-by-Step: Microsoft Entra Portal Setup
Register Your Application
-
Sign in to the Entra portal.
-
Navigate to App registrations → New registration.
-
Enter a name for your app.
-
Choose Accounts in this organizational directory only (or adjust as needed).
-
Add a redirect URI (for web apps, e.g.,
http://localhost:5000/signin-oidc
). -
Click Register.
Configure Authentication
-
In the app registration, go to Authentication.
-
Under Platform configurations, select Web or SPA.
-
Add your redirect URIs.
-
Enable ID tokens (check the box under “Implicit grant and hybrid flows”).
Create Client Secrets
-
Go to Certificates & secrets.
-
Create a new client secret → copy and store the value securely.
Assign API Permissions
-
In the app registration, go to API permissions.
-
Add permissions (Microsoft Graph or custom APIs).
-
For admin-level permissions, grant Admin consent using your parent Service Principal.
3. Two Service Principals Pattern
We’ll use two SPNs for a secure separation of concerns:
-
Parent SPN (Admin Consent) → has elevated privileges, grants access to APIs/resources.
-
Child SPN (Application Runtime) → used in app code (Java, Python, .NET) to request tokens.
Flow:
-
Application calls Entra using child SPN credentials.
-
Entra issues a JWT (ID/access token).
-
Application validates the token.
-
If valid, access is granted.
4. Code Examples
🔹 Python (MSAL)
from msal import ConfidentialClientApplication
client_id = "<CHILD_SPN_CLIENT_ID>"
client_secret = "<CHILD_SPN_SECRET>"
tenant_id = "<TENANT_ID>"
authority = f"https://login.microsoftonline.com/{tenant_id}"
scope = ["api://<API_CLIENT_ID>/.default"]
app = ConfidentialClientApplication(
client_id,
authority=authority,
client_credential=client_secret,
)
result = app.acquire_token_for_client(scopes=scope)
if "access_token" in result:
print("Access Token:", result["access_token"])
else:
print("Error:", result.get("error_description"))
🔹 Java (MSAL4J)
import com.microsoft.aad.msal4j.*;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
public class OidcAuth {
public static void main(String[] args) throws Exception {
String clientId = "<CHILD_SPN_CLIENT_ID>";
String clientSecret = "<CHILD_SPN_SECRET>";
String tenantId = "<TENANT_ID>";
String authority = "https://login.microsoftonline.com/" + tenantId;
ConfidentialClientApplication app = ConfidentialClientApplication.builder(
clientId, ClientCredentialFactory.createFromSecret(clientSecret))
.authority(authority)
.build();
ClientCredentialParameters parameters = ClientCredentialParameters.builder(
Collections.singleton("api://<API_CLIENT_ID>/.default"))
.build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(parameters);
IAuthenticationResult result = future.get();
System.out.println("Access Token: " + result.accessToken());
}
}
🔹.NET (MSAL.NET)
using Microsoft.Identity.Client;
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var clientId = "<CHILD_SPN_CLIENT_ID>";
var clientSecret = "<CHILD_SPN_SECRET>";
var tenantId = "<TENANT_ID>";
var authority = $"https://login.microsoftonline.com/{tenantId}";
var scopes = new string[] { "api://<API_CLIENT_ID>/.default" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
AuthenticationResult result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
Console.WriteLine("Access Token: " + result.AccessToken);
}
}
5. Token Validation
Token validation is essential:
-
Signature check → using Microsoft’s public keys (
/.well-known/openid-configuration
). -
Claims validation →
iss
,aud
,exp
,nonce
. -
Use libraries:
-
Python →
PyJWT
-
Java →
Nimbus JOSE + JWT
-
.NET →
System.IdentityModel.Tokens.Jwt
-
Wrapping Up
By combining OIDC with Microsoft Entra ID and a two-SPN architecture, you get:
-
Security (admin vs runtime separation)
-
Scalability (apps authenticate independently)
-
Flexibility (works across Java, Python, .NET)
Your apps can now:
-
Request and validate tokens
-
Confirm identity
-
Safely grant access
— all while aligning with Microsoft’s identity best practices.