MSAL Java Workshop

Identity-first Java apps with Microsoft Entra ID

Practical MSAL and Azure Identity guidance for Java teams, followed by a walkthrough of the Justice Evidence Portal proof of concept in this repository.

Java 17 + Spring Boot 3.4 Angular SPA + MSAL.js Managed Identity + ADLS Gen2 Updated May 1, 2026
01 Identity Design Principles

Start with token boundaries, not libraries.

MSAL is the client-side token acquisition library. In a secure Java architecture, it sits inside a broader design: a public client signs in the user, an API validates delegated access tokens, and backend resources are accessed with a workload identity.

1Identity provider: Microsoft Entra ID issues tokens for users and workloads.
2Token types: ID tokens identify users; access tokens authorize API calls.
3Authorization layers: scopes model delegated permissions; roles model app decisions.
4Secret posture: managed identity removes passwords, client secrets, and storage keys from runtime.

Choose the right flow

Browser SPAs should use authorization code with PKCE. Server-side Java web apps use confidential-client patterns. Service-to-service access should use managed identity or client credentials, depending on hosting and trust boundaries.

Validate the token you receive

An API must validate issuer, audience, signature, expiry, token version, and expected claims. Do not accept an ID token as proof that the caller can use an API.

Separate user and workload identity

The user's delegated token should authorize the API request. The API's managed identity should authorize the downstream Azure resource call. Do not forward user tokens to storage unless that is the explicit design.

02 MSAL Java Best Practices

Use MSAL where it owns the OAuth conversation.

For Java apps that sign users in or call downstream APIs, MSAL Java is the protocol-level library. For Azure resource clients, prefer Azure SDK credentials such as DefaultAzureCredential or ManagedIdentityCredential; the Azure SDK uses MSAL underneath and gives a better developer experience.

Practice Why it matters Implementation cue
Use authorization code + PKCE for browser apps SPAs are public clients and cannot safely hold a client secret. Request API scopes such as api://{api-client-id}/Evidence.Read from the SPA.
Request least-privilege scopes Scopes are the user's delegated permissions to an API. Expose narrow scopes on the API app registration instead of broad, reusable catch-all scopes.
Prefer silent token acquisition where applicable MSAL maintains token caches and can refresh tokens before expiry. In browser and confidential-client apps, attempt silent acquisition before interactive prompts when the flow supports it.
Handle Conditional Access claim challenges MFA, device compliance, and other policies can require extra claims before a token is issued. Catch token acquisition exceptions and route the user through the challenge rather than failing with a generic error.
Enable diagnostics without logging secrets Authentication failures often require correlation IDs and authority details. Keep PII logging disabled in normal environments and log correlation IDs, tenant, authority, and error codes.
Use tenant-specific authorities when possible A tenant authority reduces ambiguity and aligns issuer validation with the API. Use https://login.microsoftonline.com/{tenant-id} unless the app is intentionally multi-tenant.
Rule of thumb: MSAL acquires tokens. Spring Security validates bearer tokens. Azure Identity authenticates Azure SDK clients. Keeping those responsibilities distinct makes Java identity code easier to reason about and easier to troubleshoot.
03 Java API Authorization

A protected API is a resource server, not a login page.

A Spring Boot API should treat the incoming access token as evidence of delegated permission, then convert expected claims into local authorities. Authentication answers who called. Authorization answers what that caller may do.

Incoming request

Bearer token

SPA sends Authorization: Bearer {access_token} to the API.

->
Validation

JWT resource server

Spring validates signature, issuer, audience, expiry, and configured token metadata.

->
Decision

Scopes and roles

scp becomes SCOPE_*; roles becomes ROLE_*.

Scopes for delegated API permissions

This PoC protects case and evidence endpoints with SCOPE_Evidence.Read. That means a signed-in user must obtain an access token for the API scope before the API returns data.

App roles for application decisions

The API also maps the Entra ID roles claim into Spring authorities such as ROLE_CaseAdmin. Use roles for coarse business permissions such as case administration.

04 Managed Identity and Azure Resources

Remove secrets from the data path.

For Azure-hosted Java workloads, managed identity is usually the cleanest way to call Azure resources. The app service owns an identity in Entra ID, Azure RBAC grants that identity data-plane access, and the Java SDK obtains tokens without storing credentials.

Use managed identity in Azure

System-assigned managed identity fits single-resource ownership. User-assigned managed identity fits shared identities, slot swaps, or identities that must survive resource replacement.

Pin credential behavior in production

This PoC uses ManagedIdentityCredential when the App Service identity endpoint is present and DefaultAzureCredential for local developer scenarios.

Expect token caching differences

MSAL Java supports in-memory caching for managed identity. Distributed cache extensibility is not supported for managed identity tokens because the token belongs to an Azure resource.

Operational lesson from this repo: the first managed identity token acquisition after deployment can be noticeably slower than warm calls. For latency-sensitive first requests, warm the credential or downstream SDK client during application startup and track authentication latency in telemetry.
05 How This PoC Works

The Justice Evidence Portal uses two tokens for two different jobs.

The sample is a three-tier application: Angular SPA, Spring Boot API, and Azure Data Lake Storage Gen2. The user token never becomes a storage credential. The API validates the user token, then uses its own managed identity to read evidence files.

Justice Evidence Portal architecture Browser user authenticates with Microsoft Entra ID, calls the Angular SPA and Spring Boot API, and the API uses managed identity over a private network path to read ADLS Gen2. Azure subscription: App Service, VNet, Private Endpoint, ADLS Gen2 End user Browser Authorization code + PKCE Microsoft Entra ID App registrations Scopes and app roles Angular SPA MSAL.js Access token for API Spring Boot API JWT resource server Managed identity ADLS Gen2 Private Endpoint Shared keys disabled RBAC data plane 1 sign in 2 tokens 3 API call + bearer access token 4 MI token + DFS read 5 bytes stream back issuer + audience Private DNS resolves dfs endpoint
Step 1

User signs in

Angular uses MSAL.js with a tenant authority, redirect flow, session storage, and the API's delegated scope.

Step 2

SPA calls API

The MSAL interceptor attaches the access token when calling protected API routes.

Step 3

Spring validates JWT

The API runs as an OAuth2 resource server and requires SCOPE_Evidence.Read for case and evidence endpoints.

Step 4

API reads storage

The API builds a Data Lake client for https://{account}.dfs.core.windows.net using managed identity.

Step 5

Evidence downloads

The API streams the evidence file back as a controlled response with a download filename.

Repo area What it demonstrates Security behavior
sample-app/spa/src/app/auth-config.ts MSAL browser configuration, login request, and protected resource map. The SPA requests only the API scopes it needs and avoids persistent local token storage by using session storage.
SecurityConfig.java Spring Security JWT resource server configuration. Non-dev profiles require authentication and enforce SCOPE_Evidence.Read on API routes.
EvidenceController.java Method-level authorization for downloads. Evidence download requires the delegated read scope before any storage read is attempted.
AzureStorageConfig.java Data Lake SDK configuration with Azure Identity. App Service uses managed identity; local development can use the developer's Azure credential chain.
infra/main.bicep Azure deployment of App Service, storage, networking, monitoring, and identity permissions. Storage keys are disabled, storage access is private, and API managed identity receives data-plane RBAC.
06 Production Hardening Checklist

Keep the workshop pattern, then harden the edges.

The PoC already demonstrates a strong baseline: no shared storage keys, no SAS tokens, JWT validation, delegated API permissions, managed identity, and private storage access. Production readiness comes from tightening operations, ingress, observability, and policy.

Identity and authorization

  • Use tenant-specific issuer validation unless the app is intentionally multi-tenant.
  • Review consent grants and app role assignments regularly.
  • Keep delegated scopes narrow and align them to API operations.
  • Plan Conditional Access claim challenge handling for interactive clients.

Secrets and credentials

  • Prefer managed identity for Azure-hosted workloads.
  • Disable shared keys when using storage with Entra ID and RBAC.
  • Do not write tokens, authorization headers, or PII claims to logs.
  • Use Key Vault only for secrets that cannot be removed.

Network and storage

  • Use Private Endpoint and Private DNS for ADLS Gen2 data-plane access.
  • Route App Service outbound traffic through VNet integration.
  • Use the dfs endpoint for Data Lake workloads.
  • Put WAF or Front Door in front of public ingress for production.

Reliability and operations

  • Measure token acquisition latency and downstream SDK latency separately.
  • Warm managed identity and storage clients during startup when first-request latency matters.
  • Log correlation IDs for token failures and storage request IDs for data-plane failures.
  • Use Application Insights and alerts for 401, 403, 5xx, and cold-start spikes.
07 Microsoft Documentation References

Official docs to keep open while building.

The recommendations above are grounded in Microsoft identity platform, MSAL Java, Azure Identity, Spring Boot, managed identity, and Azure Storage documentation.