End-to-End Guide: Implementing OpenID Connect (OIDC) with Microsoft Entra ID

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:

  1. What OIDC is and how it works.

  2. Step-by-step setup in the Microsoft Entra portal.

  3. A two–Service Principal architecture.

  4. Code samples in Python, Java, and .NET.

  5. 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

  1. App Registration

    • The app is registered in Microsoft Entra ID so it can request tokens.

  2. Discovery

    • The app fetches Entra’s discovery document (/.well-known/openid-configuration) to get endpoints and public keys.

  3. Authentication Request

    • The app redirects the user (or service) to the /authorize endpoint with parameters like client_idscope=openidredirect_uri, and a nonce.

  4. User Sign-In

    • The user enters their credentials, MFA, or uses single sign-on (SSO).

  5. Token Issuance

    • Entra ID issues an ID token (plus optionally an access token).

  6. Token Validation

    • The application validates the ID token’s signature and claims.

  7. Session Established

    • The app trusts the token and authenticates the user.

2. Step-by-Step: Microsoft Entra Portal Setup

Register Your Application

  1. Sign in to the Entra portal.

  2. Navigate to App registrationsNew registration.

  3. Enter a name for your app.

  4. Choose Accounts in this organizational directory only (or adjust as needed).

  5. Add a redirect URI (for web apps, e.g., http://localhost:5000/signin-oidc).

  6. Click Register.


Configure Authentication

  1. In the app registration, go to Authentication.

  2. Under Platform configurations, select Web or SPA.

  3. Add your redirect URIs.

  4. Enable ID tokens (check the box under “Implicit grant and hybrid flows”).


Create Client Secrets

  1. Go to Certificates & secrets.

  2. Create a new client secret → copy and store the value securely.


Assign API Permissions

  1. In the app registration, go to API permissions.

  2. Add permissions (Microsoft Graph or custom APIs).

  3. 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:

  1. Application calls Entra using child SPN credentials.

  2. Entra issues a JWT (ID/access token).

  3. Application validates the token.

  4. 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 validationiss, 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top