Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal static class LoggingEventId

// MergedOptions EventIds 500+
public static readonly EventId AuthorityIgnored = new EventId(500, "AuthorityIgnored");
public static readonly EventId AuthorityUsedConsiderInstanceTenantId = new EventId(501, "AuthorityUsedConsiderInstanceTenantId");

#pragma warning restore IDE1006 // Naming Styles
}
Expand Down
85 changes: 60 additions & 25 deletions src/Microsoft.Identity.Web.TokenAcquisition/MergedOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ namespace Microsoft.Identity.Web
/// Options for configuring authentication using Azure Active Directory. It has both AAD and B2C configuration attributes.
/// Merges the MicrosoftIdentityWebOptions and the ConfidentialClientApplicationOptions.
/// </summary>
/*
* Used by Microsoft.Identity.Web, Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
/*
* Used by Microsoft.Identity.Web, Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
internal sealed class MergedOptions : MicrosoftIdentityOptions
{
private ConfidentialClientApplicationOptions? _confidentialClientApplicationOptions;
Expand Down Expand Up @@ -63,18 +63,18 @@ public ConfidentialClientApplicationOptions ConfidentialClientApplicationOptions
public LogLevel LogLevel { get; set; }
public string? RedirectUri { get; set; }
public bool EnableCacheSynchronization { get; set; }
/*
* Used by Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
/*
* Used by Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
internal bool MergedWithCca { get; set; }
// This is for supporting for CIAM authorities including custom url domains, see https://github.com/AzureAD/microsoft-identity-web/issues/2690
/*
* Used by Microsoft.Identity.Web
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
/*
* Used by Microsoft.Identity.Web
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
internal bool PreserveAuthority { get; set; }

/// <summary>
Expand Down Expand Up @@ -353,11 +353,11 @@ internal static void UpdateMergedOptionsFromMicrosoftIdentityOptions(MicrosoftId
}
}

/*
* Used by Microsoft.Identity.Web
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
/*
* Used by Microsoft.Identity.Web
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
internal static void UpdateMergedOptionsFromConfidentialClientApplicationOptions(ConfidentialClientApplicationOptions confidentialClientApplicationOptions, MergedOptions mergedOptions)
{
mergedOptions.MergedWithCca = true;
Expand Down Expand Up @@ -466,11 +466,11 @@ internal static void UpdateConfidentialClientApplicationOptionsFromMergedOptions
}
}

/*
* Used by Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
/*
* Used by Microsoft.Identity.Web.OWIN
* Any changes to this member (including removal) can cause runtime failures.
* Treat as a public member.
*/
internal static void ParseAuthorityIfNecessary(MergedOptions mergedOptions, IdWebLogger.ILogger? logger = null)
{
// Check if Authority is configured but being ignored due to Instance/TenantId taking precedence
Expand All @@ -492,6 +492,19 @@ internal static void ParseAuthorityIfNecessary(MergedOptions mergedOptions, IdWe

if (string.IsNullOrEmpty(mergedOptions.TenantId) && string.IsNullOrEmpty(mergedOptions.Instance) && !string.IsNullOrEmpty(mergedOptions.Authority))
{
// Emit a warning whenever the single-string 'Authority' option is being used to derive
// Instance/TenantId. The 'Authority' option targets vanilla OIDC / CIAM scenarios and
// routes through MSAL.WithOidcAuthority(); first-party (1P) callers (e.g. services
// using Microsoft Identity Service Essentials / MISE) should configure 'Instance' +
// 'TenantId' separately so the request flows through MSAL.WithAuthority(). Third-party
// (3P) callers using CIAM / ADFS / generic OIDC can safely ignore this warning.
// Microsoft.Identity.Web is a 3P-targeted library and cannot reliably tell whether the
// caller is 1P or 3P at runtime, so we emit a hint rather than throwing.
if (logger != null)
{
MergedOptionsLogging.AuthorityUsedConsiderInstanceTenantId(logger, mergedOptions.Authority!);
}

ReadOnlySpan<char> doubleSlash = "//".AsSpan();
ReadOnlySpan<char> authoritySpan = mergedOptions.Authority.AsSpan().TrimEnd('/');
int doubleSlashIndex = authoritySpan.IndexOf(doubleSlash);
Expand All @@ -504,6 +517,28 @@ internal static void ParseAuthorityIfNecessary(MergedOptions mergedOptions, IdWe
int indexVersion = authoritySpan.Slice(indexTenant + 1).IndexOf('/');
int indexEndOfTenant = indexVersion == -1 ? authoritySpan.Length : indexVersion + indexTenant + 1;

// dSTS authorities have the shape https://{host}/dstsv2/{tenantGuid}, i.e. TWO path
// segments instead of the AAD-style single segment. The single 'Authority' string
// is reserved for vanilla OIDC / CIAM scenarios, which route through
// MSAL.WithOidcAuthority() — a path that is incompatible with dSTS. dSTS users MUST
// configure 'Instance' and 'TenantId' separately so that the request flows through
// MSAL.WithAuthority() instead.
//
// Detecting the "dstsv2" path segment here lets us bail with a clear, actionable
// error message instead of letting the generic AAD parser silently drop the
// tenant GUID, which would surface later as MSAL's opaque
// "The DSTS authority URI should have at least 2 segments..."
ReadOnlySpan<char> firstPathSegment = authoritySpan.Slice(indexTenant + 1, indexEndOfTenant - indexTenant - 1);
if (firstPathSegment.Equals("dstsv2".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
"Configuring a dSTS authority via the single 'Authority' option is not supported. " +
"The 'Authority' option targets vanilla OIDC / CIAM scenarios and routes through " +
"MSAL.WithOidcAuthority(), which is incompatible with dSTS. " +
"For dSTS, configure 'Instance' (e.g. \"https://{host}/dstsv2\") and 'TenantId' " +
"(the dSTS tenant GUID) separately so the request flows through MSAL.WithAuthority().");
}

// In CIAM and B2C, customers will use "authority", not Instance and TenantId
mergedOptions.Instance = mergedOptions.PreserveAuthority ? mergedOptions.Authority! : authoritySpan.Slice(0, indexTenant).ToString();
mergedOptions.TenantId = mergedOptions.PreserveAuthority ? null : authoritySpan.Slice(indexTenant + 1, indexEndOfTenant - indexTenant - 1).ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,30 @@ public static void AuthorityIgnored(
{
s_authorityIgnored(logger, authority, instance, tenantId, null);
}

private static readonly Action<ILogger, string, Exception?> s_authorityUsedConsiderInstanceTenantId =
LoggerMessage.Define<string>(
LogLevel.Warning,
LoggingEventId.AuthorityUsedConsiderInstanceTenantId,
"[MsIdWeb] The 'Authority' option ('{Authority}') is configured. " +
"'Authority' is intended for vanilla OIDC / CIAM scenarios (3P) and routes through MSAL.WithOidcAuthority(). " +
"First-party (1P) callers — e.g. services using Microsoft Identity Service Essentials (MISE) — should NOT use 'Authority'; " +
"configure 'Instance' (e.g. \"https://login.microsoftonline.com\" or \"https://{{host}}/dstsv2\") and 'TenantId' separately, " +
"which routes through MSAL.WithAuthority() and works correctly with eSTS, dSTS, and B2C. " +
"Third-party (3P) callers using CIAM, ADFS, or generic OIDC issuers can safely ignore this warning.");

/// <summary>
/// Logs a warning when an application configures the single-string <c>Authority</c> option,
/// hinting that first-party (1P) callers (e.g. MISE) should use <c>Instance</c> + <c>TenantId</c> instead.
/// Third-party (3P) callers using CIAM / ADFS / generic OIDC can safely ignore the warning.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <param name="authority">The Authority value being parsed.</param>
public static void AuthorityUsedConsiderInstanceTenantId(
ILogger logger,
string authority)
{
s_authorityUsedConsiderInstanceTenantId(logger, authority, null);
}
}
}
Loading
Loading