//called from the web service with token, IP address of user and terminal name of theusing userDapper;
public async Task<ApiResponse> ValidateJwt(string token, string ip, stringusing terminalName)HealthLibrary;
using {Microsoft.IdentityModel.Protocols.OpenIdConnect;
ifusing (stringMicrosoft.IsNullOrEmpty(token))IdentityModel.Protocols;
using {Microsoft.IdentityModel.Tokens;
throw newusing Exception("NoSystem;
using tokenSystem.Collections.Generic;
using wasSystem.Configuration;
using supplied");System.Data;
using System.Data.SqlClient;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Web.Configuration;
using System.Threading.Tasks;
using }System.Text.RegularExpressions;
// Get list of valid Entra tenant IDs
List<string> validTenantIds = validTenantIDs();
var claimsPrincipal = await ValidateToken(token, validTenantIds);
//called Extractfrom Tenantthe ID
web service with token, IP address of user varand tenantIdterminal =name claimsPrincipal.Claims.FirstOrDefault(cof =>the c.Typeuser
== "iss")?.Value;
public async ifTask<ApiResponse> ValidateJwt(string.IsNullOrEmpty(tenantId) token, string ip, string terminalName)
{
throw newif Exception("Nostring.IsNullOrEmpty(token))
Entra tenant ID supplied");
}{
tenantId = extractGuid throw new Exception(tenantId"No token was supplied");
}
// Extract Email
// Get list of valid Entra vartenant emailIDs
= claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")?.Value;
List<string> validTenantIds if= (string.IsNullOrEmptyvalidTenantIDs(email));
{
var claimsPrincipal throw= newawait ExceptionValidateToken("Emailtoken, missing"validTenantIds);
}
return GenerateAppKey // Extract Tenant ID
var tenantId = claimsPrincipal.Claims.FirstOrDefault(email,c => c.Type == "iss")?.Value;
if (string.IsNullOrEmpty(tenantId,))
ip, terminalName {
throw new Exception("No Entra tenant ID supplied");
}
tenantId = extractGuid(tenantId);
//Gets valid tenant IDs from the database and links them with the Entra issuer URL
private List<string> validTenantIDs()
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ssoConnection"].ConnectionString))
// Extract {Email
var procedureemail = WebConfigurationManager.AppSettings["DatabaseName"] + "claimsPrincipal.dboClaims.SSO_GetValidTenants";
var p = new DynamicParametersFirstOrDefault();
var resultsc ==> connectionc.Query(procedure,Type p,== commandType"http: CommandType//schemas.StoredProcedure, commandTimeout: 120)xmlsoap.ToList(org/ws/2005/05/identity/claims/upn");?.Value;
List<string> list = newif List<string>();
foreach string.IsNullOrEmpty(var result in resultsemail))
{
list.Add("https://sts.windows.net/" +throw result.MicrosoftEntraTenantIDnew +Exception("Email "/"missing");
}
return list;
}
}
//Extract the GUID from the Entra URL for validation purposes later
private static stringreturn extractGuidGenerateAppKey(string url)
{
//extract the GUID from the Entra URL
varemail, patterntenantId, =ip, @"https:\/\/sts\.windows\.net\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}terminalName)\/";;
var match = Regex.Match(url, pattern);}
if//Gets (match.Successvalid &&tenant match.Groups.CountIDs >from 1the database and links them with the Entra issuer URL
private List<string> validTenantIDs()
{
returnusing match(SqlConnection connection = new SqlConnection(ConfigurationManager.Groups[1]ConnectionStrings["ssoConnection"].Value;ConnectionString))
{
var procedure = WebConfigurationManager.AppSettings["DatabaseName"] + ".dbo.SSO_GetValidTenants";
var p = new DynamicParameters();
var results = connection.Query(procedure, p, commandType: CommandType.StoredProcedure, commandTimeout: 120).ToList();
List<string> list = new List<string>();
foreach (var result in results)
{
list.Add("https://sts.windows.net/" + result.MicrosoftEntraTenantID + "/");
}
return list;
}
}
throw new//Extract Exception("Nothe GUID could be identified from the AzureEntra URL. Please contact your administrator for assistance");
validation purposes }
later
//validates the token and returns a ClaimsPrincipal object
private async Task<static ClaimsPrincipal>string ValidateTokenextractGuid(string token, List<string> validTenantIdsurl)
{
string invalidTokenMessage = "A sign in was attempted for an invalid Entra tenant."
+ Environment.NewLine
+ "Your site may not be set up for Single Sign On - please contact your Administrator for more details.";
try
{
var tokenHandler = new JwtSecurityTokenHandler();
//extract the GUID from the Entra URL
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
var pattern = "https@"https:\/\/login.microsoftonlinests\.com/common/v2windows\.0net\/.well([0-known/openid9a-configuration",fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/";
newvar OpenIdConnectConfigurationRetrievermatch = Regex.Match()url, pattern);
var openIdConfig =if configurationManager.GetConfigurationAsync(CancellationTokenmatch.None);
varSuccess validationParameters&& =match.Groups.Count new> TokenValidationParameters1)
{
IssuerSigningKeys =return openIdConfig.Resultmatch.SigningKeys,
ValidIssuers = validTenantIds,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = "api://" + WebConfigurationManagerGroups[1].AppSettings["EntraClientID"]Value;
};
returnthrow tokenHandler.ValidateTokennew Exception(token,"No validationParameters,GUID outcould _be identified from the Azure URL. Please contact your administrator for assistance");
}
catch//validates the token and returns a ClaimsPrincipal object
private async Task< ClaimsPrincipal> ValidateToken(SecurityTokenInvalidIssuerExceptionstring token, List<string> validTenantIds)
{
string invalidTokenMessage = "A sign in was attempted for an invalid Entra tenant."
+ Environment.NewLine
+ "Your site may not be set up for Single Sign On - please contact your Administrator for more details.";
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.Result.SigningKeys,
ValidIssuers = validTenantIds,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = "api://" + WebConfigurationManager.AppSettings["EntraClientID"]
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
catch(SecurityTokenInvalidIssuerException)
{
throw new Exception(invalidTokenMessage);
}
}
}
//called from the web service with token, IP address of user and terminal name of the user
public async Task<ApiResponse> ValidateJwt(string token, string ip, string terminalName)
{
if (string.IsNullOrEmpty(token))
{
throw new Exception("No token was supplied");
}
// Get list of valid Entra tenant IDs
List<string> validTenantIds = validTenantIDs();
var claimsPrincipal = await ValidateToken(token, validTenantIds);
// Extract Tenant ID
var tenantId = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "iss")?.Value;
if (string.IsNullOrEmpty(tenantId))
{
throw new Exception("No Entra tenant ID supplied");
}
tenantId = extractGuid(tenantId);
// Extract Email
var email = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")?.Value;
if (string.IsNullOrEmpty(email))
{
throw new Exception("Email missing");
}
return GenerateAppKey(email, tenantId, ip, terminalName);
}
//Gets valid tenant IDs from the database and links them with the Entra issuer URL
private List<string> validTenantIDs()
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ssoConnection"].ConnectionString))
{
var procedure = WebConfigurationManager.AppSettings["DatabaseName"] + ".dbo.SSO_GetValidTenants";
var p = new DynamicParameters();
var results = connection.Query(procedure, p, commandType: CommandType.StoredProcedure, commandTimeout: 120).ToList();
List<string> list = new List<string>();
foreach (var result in results)
{
list.Add("https://sts.windows.net/" + result.MicrosoftEntraTenantID + "/");
}
return list;
}
}
//Extract the GUID from the Entra URL for validation purposes later
private static string extractGuid(string url)
{
//extract the GUID from the Entra URL
var pattern = @"https:\/\/sts\.windows\.net\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/";
var match = Regex.Match(url, pattern);
if (match.Success && match.Groups.Count > 1)
{
return match.Groups[1].Value;
}
throw new Exception("No GUID could be identified from the Azure URL. Please contact your administrator for assistance");
}
//validates the token and returns a ClaimsPrincipal object
private async Task< ClaimsPrincipal> ValidateToken(string token, List<string> validTenantIds)
{
string invalidTokenMessage = "A sign in was attempted for an invalid Entra tenant."
+ Environment.NewLine
+ "Your site may not be set up for Single Sign On - please contact your Administrator for more details.";
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.Result.SigningKeys,
ValidIssuers = validTenantIds,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = "api://" + WebConfigurationManager.AppSettings["EntraClientID"]
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
catch(SecurityTokenInvalidIssuerException)
{
throw new Exception(invalidTokenMessage);
}
}
using Dapper;
using HealthLibrary;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Web.Configuration;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
//called from the web service with token, IP address of user and terminal name of the user
public async Task<ApiResponse> ValidateJwt(string token, string ip, string terminalName)
{
if (string.IsNullOrEmpty(token))
{
throw new Exception("No token was supplied");
}
// Get list of valid Entra tenant IDs
List<string> validTenantIds = validTenantIDs();
var claimsPrincipal = await ValidateToken(token, validTenantIds);
// Extract Tenant ID
var tenantId = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "iss")?.Value;
if (string.IsNullOrEmpty(tenantId))
{
throw new Exception("No Entra tenant ID supplied");
}
tenantId = extractGuid(tenantId);
// Extract Email
var email = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")?.Value;
if (string.IsNullOrEmpty(email))
{
throw new Exception("Email missing");
}
return GenerateAppKey(email, tenantId, ip, terminalName);
}
//Gets valid tenant IDs from the database and links them with the Entra issuer URL
private List<string> validTenantIDs()
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ssoConnection"].ConnectionString))
{
var procedure = WebConfigurationManager.AppSettings["DatabaseName"] + ".dbo.SSO_GetValidTenants";
var p = new DynamicParameters();
var results = connection.Query(procedure, p, commandType: CommandType.StoredProcedure, commandTimeout: 120).ToList();
List<string> list = new List<string>();
foreach (var result in results)
{
list.Add("https://sts.windows.net/" + result.MicrosoftEntraTenantID + "/");
}
return list;
}
}
//Extract the GUID from the Entra URL for validation purposes later
private static string extractGuid(string url)
{
//extract the GUID from the Entra URL
var pattern = @"https:\/\/sts\.windows\.net\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/";
var match = Regex.Match(url, pattern);
if (match.Success && match.Groups.Count > 1)
{
return match.Groups[1].Value;
}
throw new Exception("No GUID could be identified from the Azure URL. Please contact your administrator for assistance");
}
//validates the token and returns a ClaimsPrincipal object
private async Task< ClaimsPrincipal> ValidateToken(string token, List<string> validTenantIds)
{
string invalidTokenMessage = "A sign in was attempted for an invalid Entra tenant."
+ Environment.NewLine
+ "Your site may not be set up for Single Sign On - please contact your Administrator for more details.";
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.Result.SigningKeys,
ValidIssuers = validTenantIds,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = "api://" + WebConfigurationManager.AppSettings["EntraClientID"]
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
catch(SecurityTokenInvalidIssuerException)
{
throw new Exception(invalidTokenMessage);
}
}
Does my C# code to validate Validate Microsoft Entra JWT have any obvious vulnerabilities?
Does my C# code to validate Microsoft Entra JWT have any obvious vulnerabilities?
I am implementing SSO in my application via Microsoft Entra. It has a client application with Cloud APIs.
The Client passes an Entra JWT to the server obtained from the MS Entra libraries. My database has a list of Users with each user under a tenant. Each tenant has only one possible Entra tenant ID, and this list is collected to tell the validator what tenants to accept in the below code (and later used with the email address to validate that the user is allowed to log in from that Entra tenant).
So for the purposes of my app's security model, is there an obvious way to pass a JWT to the below code that impersonates both the issuer and email address and could therefore send a correct tenant ID and email to the authentication code?
//called from the web service with token, IP address of user and terminal name of the user
public async Task<ApiResponse> ValidateJwt(string token, string ip, string terminalName)
{
if (string.IsNullOrEmpty(token))
{
throw new Exception("No token was supplied");
}
// Get list of valid Entra tenant IDs
List<string> validTenantIds = validTenantIDs();
var claimsPrincipal = await ValidateToken(token, validTenantIds);
// Extract Tenant ID
var tenantId = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "iss")?.Value;
if (string.IsNullOrEmpty(tenantId))
{
throw new Exception("No Entra tenant ID supplied");
}
tenantId = extractGuid(tenantId);
// Extract Email
var email = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn")?.Value;
if (string.IsNullOrEmpty(email))
{
throw new Exception("Email missing");
}
return GenerateAppKey(email, tenantId, ip, terminalName);
}
//Gets valid tenant IDs from the database and links them with the Entra issuer URL
private List<string> validTenantIDs()
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ssoConnection"].ConnectionString))
{
var procedure = WebConfigurationManager.AppSettings["DatabaseName"] + ".dbo.SSO_GetValidTenants";
var p = new DynamicParameters();
var results = connection.Query(procedure, p, commandType: CommandType.StoredProcedure, commandTimeout: 120).ToList();
List<string> list = new List<string>();
foreach (var result in results)
{
list.Add("https://sts.windows.net/" + result.MicrosoftEntraTenantID + "/");
}
return list;
}
}
//Extract the GUID from the Entra URL for validation purposes later
private static string extractGuid(string url)
{
//extract the GUID from the Entra URL
var pattern = @"https:\/\/sts\.windows\.net\/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\/";
var match = Regex.Match(url, pattern);
if (match.Success && match.Groups.Count > 1)
{
return match.Groups[1].Value;
}
throw new Exception("No GUID could be identified from the Azure URL. Please contact your administrator for assistance");
}
//validates the token and returns a ClaimsPrincipal object
private async Task< ClaimsPrincipal> ValidateToken(string token, List<string> validTenantIds)
{
string invalidTokenMessage = "A sign in was attempted for an invalid Entra tenant."
+ Environment.NewLine
+ "Your site may not be set up for Single Sign On - please contact your Administrator for more details.";
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var openIdConfig = configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = openIdConfig.Result.SigningKeys,
ValidIssuers = validTenantIds,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidAudience = "api://" + WebConfigurationManager.AppSettings["EntraClientID"]
};
return tokenHandler.ValidateToken(token, validationParameters, out _);
}
catch(SecurityTokenInvalidIssuerException)
{
throw new Exception(invalidTokenMessage);
}
}
lang-cs