Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Validate incoming bearer tokens by forwarding them to the Microsoft Entra SDK for AgentID's /Validate endpoint, then extract the returned claims to make authorization decisions. This guide shows you how to implement token validation middleware and make authorization decisions based on scopes or roles.
Prerequisites
- An Azure account with an active subscription. Create an account for free.
- Microsoft Entra SDK for AgentID deployed and running with network access from your application. See Installation Guide for setup instructions.
- Registered application in Microsoft Entra ID - Register a new app in the Microsoft Entra admin center, configured for Accounts in this organizational directory only. Refer to Register an application for more details. Record the following values from the application Overview page:
- Application (client) ID
- Directory (tenant) ID
- Configure an App ID URI in the Expose an API section (used as the audience for token validation)
- Bearer tokens from authenticated clients - Your application must receive tokens from client applications through OAuth 2.0 flows.
- Appropriate permissions in Microsoft Entra ID - Your account must have permissions to register applications and configure authentication settings.
Configuration
To validate tokens for your API, configure the Microsoft Entra SDK for AgentID with your Microsoft Entra ID tenant information.
env:
- name: AzureAd__Instance
value: "https://login.microsoftonline.com/"
- name: AzureAd__TenantId
value: "your-tenant-id"
- name: AzureAd__ClientId
value: "your-api-client-id"
- name: AzureAd__Audience
value: "api://your-api-id"
TypeScript/Node.js
The following implementation shows how to create a token validation middleware that integrates with the Microsoft Entra SDK for AgentID using TypeScript or JavaScript. This middleware checks every incoming request for a valid bearer token and extracts claims for use in your route handlers:
import fetch from 'node-fetch';
interface ValidateResponse {
protocol: string;
token: string;
claims: {
aud: string;
iss: string;
oid: string;
sub: string;
tid: string;
upn?: string;
scp?: string;
roles?: string[];
[key: string]: any;
};
}
async function validateToken(authorizationHeader: string): Promise<ValidateResponse> {
const sidecarUrl = process.env.SIDECAR_URL || 'http://localhost:5000';
const response = await fetch(`${sidecarUrl}/Validate`, {
headers: {
'Authorization': authorizationHeader
}
});
if (!response.ok) {
throw new Error(`Token validation failed: ${response.statusText}`);
}
return await response.json() as ValidateResponse;
}
The following snippet demonstrates how to use the validateToken function in an Express.js middleware to protect API endpoints:
// Express.js middleware example
import express from 'express';
const app = express();
// Token validation middleware
async function requireAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'No authorization token provided' });
}
try {
const validation = await validateToken(authHeader);
// Attach claims to request object
req.user = {
id: validation.claims.oid,
upn: validation.claims.upn,
tenantId: validation.claims.tid,
scopes: validation.claims.scp?.split(' ') || [],
roles: validation.claims.roles || [],
claims: validation.claims
};
next();
} catch (error) {
console.error('Token validation failed:', error);
return res.status(401).json({ error: 'Invalid token' });
}
}
// Protected endpoint
app.get('/api/protected', requireAuth, (req, res) => {
res.json({
message: 'Access granted',
user: {
id: req.user.id,
upn: req.user.upn
}
});
});
// Scope-based authorization
app.get('/api/admin', requireAuth, (req, res) => {
if (!req.user.roles.includes('Admin')) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
res.json({ message: 'Admin access granted' });
});
app.listen(8080);
Python
The following Python snippet uses Flask decorators to wrap route handlers with token validation. This decorator extracts the bearer token from the Authorization header, validates it with the Microsoft Entra SDK for AgentID, and makes the claims available in your route:
import os
import requests
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
def validate_token(authorization_header: str) -> dict:
"""Validate token using the SDK."""
sidecar_url = os.getenv('SIDECAR_URL', 'http://localhost:5000')
response = requests.get(
f"{sidecar_url}/Validate",
headers={'Authorization': authorization_header}
)
if not response.ok:
raise Exception(f"Token validation failed: {response.text}")
return response.json()
# Token validation decorator
def require_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({'error': 'No authorization token provided'}), 401
try:
validation = validate_token(auth_header)
# Attach user info to Flask's g object
from flask import g
g.user = {
'id': validation['claims']['oid'],
'upn': validation['claims'].get('upn'),
'tenant_id': validation['claims']['tid'],
'scopes': validation['claims'].get('scp', '').split(' '),
'roles': validation['claims'].get('roles', []),
'claims': validation['claims']
}
return f(*args, **kwargs)
except Exception as e:
print(f"Token validation failed: {e}")
return jsonify({'error': 'Invalid token'}), 401
return decorated_function
# Protected endpoint
@app.route('/api/protected')
@require_auth
def protected():
from flask import g
return jsonify({
'message': 'Access granted',
'user': {
'id': g.user['id'],
'upn': g.user['upn']
}
})
# Role-based authorization
@app.route('/api/admin')
@require_auth
def admin():
from flask import g
if 'Admin' not in g.user['roles']:
return jsonify({'error': 'Insufficient permissions'}), 403
return jsonify({'message': 'Admin access granted'})
if __name__ == '__main__':
app.run(port=8080)
Go
The following Go implementation demonstrates token validation using the standard HTTP handler pattern. This middleware approach extracts bearer tokens from the Authorization header, validates them with the Microsoft Entra SDK for AgentID, and stores user information in request headers for use in downstream handlers:
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
)
type ValidateResponse struct {
Protocol string `json:"protocol"`
Token string `json:"token"`
Claims map[string]interface{} `json:"claims"`
}
type User struct {
ID string
UPN string
TenantID string
Scopes []string
Roles []string
Claims map[string]interface{}
}
func validateToken(authHeader string) (*ValidateResponse, error) {
sidecarURL := os.Getenv("SIDECAR_URL")
if sidecarURL == "" {
sidecarURL = "http://localhost:5000"
}
req, err := http.NewRequest("GET", fmt.Sprintf("%s/Validate", sidecarURL), nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", authHeader)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("token validation failed: %s", resp.Status)
}
var validation ValidateResponse
if err := json.NewDecoder(resp.Body).Decode(&validation); err != nil {
return nil, err
}
return &validation, nil
}
// Middleware for token validation
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "No authorization token provided", http.StatusUnauthorized)
return
}
validation, err := validateToken(authHeader)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Extract user information from claims
user := &User{
ID: validation.Claims["oid"].(string),
TenantID: validation.Claims["tid"].(string),
Claims: validation.Claims,
}
if upn, ok := validation.Claims["upn"].(string); ok {
user.UPN = upn
}
if scp, ok := validation.Claims["scp"].(string); ok {
user.Scopes = strings.Split(scp, " ")
}
if roles, ok := validation.Claims["roles"].([]interface{}); ok {
for _, role := range roles {
user.Roles = append(user.Roles, role.(string))
}
}
// Store user in context (simplified - use context.Context in production)
r.Header.Set("X-User-ID", user.ID)
r.Header.Set("X-User-UPN", user.UPN)
next(w, r)
}
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"message": "Access granted",
"user": map[string]string{
"id": r.Header.Get("X-User-ID"),
"upn": r.Header.Get("X-User-UPN"),
},
})
}
func main() {
http.HandleFunc("/api/protected", requireAuth(protectedHandler))
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}
C#
The following C# implementation demonstrates token validation using ASP.NET Core middleware. This approach uses dependency injection to access the token validation service, extracts bearer tokens from the Authorization header, validates them with the Microsoft Entra SDK for AgentID, and stores user claims in the HttpContext for use in controllers:
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
public class ValidateResponse
{
public string Protocol { get; set; }
public string Token { get; set; }
public JsonElement Claims { get; set; }
}
public class TokenValidationService
{
private readonly HttpClient _httpClient;
private readonly string _sidecarUrl;
public TokenValidationService(IHttpClientFactory httpClientFactory, IConfiguration config)
{
_httpClient = httpClientFactory.CreateClient();
_sidecarUrl = config["SIDECAR_URL"] ?? "http://localhost:5000";
}
public async Task<ValidateResponse> ValidateTokenAsync(string authorizationHeader)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"{_sidecarUrl}/Validate");
request.Headers.Add("Authorization", authorizationHeader);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<ValidateResponse>();
}
}
// Middleware example
public class TokenValidationMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenValidationService _validationService;
public TokenValidationMiddleware(RequestDelegate next, TokenValidationService validationService)
{
_next = next;
_validationService = validationService;
}
public async Task InvokeAsync(HttpContext context)
{
var authHeader = context.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(authHeader))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new { error = "No authorization token" });
return;
}
try
{
var validation = await _validationService.ValidateTokenAsync(authHeader);
// Store claims in HttpContext.Items for use in controllers
context.Items["UserClaims"] = validation.Claims;
context.Items["UserId"] = validation.Claims.GetProperty("oid").GetString();
await _next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 401;
await context.Response.WriteAsJsonAsync(new { error = "Invalid token" });
}
}
}
// Controller example
[ApiController]
[Route("api")]
public class ProtectedController : ControllerBase
{
[HttpGet("protected")]
public IActionResult GetProtected()
{
var userId = HttpContext.Items["UserId"] as string;
return Ok(new
{
message = "Access granted",
user = new { id = userId }
});
}
}
Extracting specific claims
After validating a token, you can extract the claims to make authorization decisions in your application. The /Validate endpoint returns a claims object with the following information:
{
"protocol": "Bearer",
"claims": {
"oid": "user-object-id",
"upn": "user@contoso.com",
"tid": "tenant-id",
"scp": "User.Read Mail.Read",
"roles": ["Admin"]
}
}
Common claims include:
oid: Object identifier (unique user ID) in your Microsoft Entra ID tenantupn: User principal name (typically email format)tid: Tenant ID where the user belongsscp: Delegated scopes the user granted to your applicationroles: Application roles assigned to the user
The following examples show how to extract specific claims from the validation response:
User Identity:
// Extract user identity
const userId = validation.claims.oid; // Object ID
const userPrincipalName = validation.claims.upn; // User Principal Name
const tenantId = validation.claims.tid; // Tenant ID
Scopes and Roles:
// Extract scopes (delegated permissions)
const scopes = validation.claims.scp?.split(' ') || [];
// Check for specific scope
if (scopes.includes('User.Read')) {
// Allow access
}
// Extract roles (application permissions)
const roles = validation.claims.roles || [];
// Check for specific role
if (roles.includes('Admin')) {
// Allow admin access
}
Authorization patterns
After validating tokens, you can enforce authorization based on either delegated scopes (permissions granted by the user) or application roles (assigned by your tenant administrator). Choose the pattern that matches your authorization model:
Scope-based authorization
Check if the user token includes required scopes before granting access:
function requireScopes(requiredScopes: string[]) {
return async (req, res, next) => {
const validation = await validateToken(req.headers.authorization);
const userScopes = validation.claims.scp?.split(' ') || [];
const hasAllScopes = requiredScopes.every(s => userScopes.includes(s));
if (!hasAllScopes) {
return res.status(403).json({ error: 'Insufficient scopes' });
}
next();
};
}
app.get('/api/mail', requireScopes(['Mail.Read']), (req, res) => {
res.json({ message: 'Mail access granted' });
});
Role-based authorization
Check if the user has required application roles:
function requireRoles(requiredRoles: string[]) {
return async (req, res, next) => {
const validation = await validateToken(req.headers.authorization);
const userRoles = validation.claims.roles || [];
const hasRole = requiredRoles.some(r => userRoles.includes(r));
if (!hasRole) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
app.delete('/api/resource', requireRoles(['Admin']), (req, res) => {
res.json({ message: 'Resource deleted' });
});
Error handling
Token validation can fail for several reasons: the token may be expired, invalid, or missing required scopes. Implement error handling that distinguishes between different failure scenarios so you can respond appropriately:
async function validateTokenSafely(authHeader: string): Promise<ValidateResponse | null> {
try {
return await validateToken(authHeader);
} catch (error) {
if (error.message.includes('401')) {
console.error('Token is invalid or expired');
} else if (error.message.includes('403')) {
console.error('Token missing required scopes');
} else {
console.error('Token validation error:', error.message);
}
return null;
}
}
Common Validation Errors
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Invalid or expired token | Request new token from client |
| 403 Forbidden | Missing required scopes | Update scope configuration or token request |
| 400 Bad Request | Malformed authorization header | Check header format: ****** |
Response Structure
The /Validate endpoint returns:
{
"protocol": "Bearer",
"token": "******",
"claims": {
"aud": "api://your-api-id",
"iss": "https://sts.windows.net/tenant-id/",
"iat": 1234567890,
"nbf": 1234567890,
"exp": 1234571490,
"oid": "user-object-id",
"sub": "subject",
"tid": "tenant-id",
"upn": "user@contoso.com",
"scp": "User.Read Mail.Read",
"roles": ["Admin"]
}
}
Best Practices
- Validate Early: Validate tokens at the API gateway or entry point
- Check Scopes: Always verify token has required scopes for the operation
- Log Failures: Log validation failures for security monitoring
- Handle Errors: Provide clear error messages for debugging
- Use Middleware: Implement validation as middleware for consistency
- Secure SDK: Ensure the SDK is only accessible from your application
Next steps
After validating tokens, you may need to: