Skip to content

Commit

Permalink
Auth schemes (#1201)
Browse files Browse the repository at this point in the history
* multiple auth schemes (#1157)

* initial commit for auth schemes

* update tests part 1

* add basic web app for testing

* updates to dev app

* more updates to dev app and downstreamapi

* update from merge conflict

* update app integration tests

* few more updates

* merge options partial idea (#1162)

* merge options partial idea

* fix tests

* fix warnings

* fix validate options

* fix credscan (#1172)

* fix policheck

* more updates to mergedoptions (#1177)

* another possiblity for merged options

* fix issue w/post configure

* fix tests

* fix a few more tests

* fix warnings

* fix integration tests

* update merged options

* fix warnings

* fix push

* add scheme to graph call, clean up unused method, add randomness to d… (#1180)

* add scheme to graph call, clean up unused method, add randomness to dev ap

* fix warnings

* PR feedback & fix sign-out

* add auth scheme integration tests (#1189)

* add auth scheme integration tests

* few more updates

* few more updates

* Adding the merged options generator

* Adding generated methods for merged options

* Removing code that isn't necessary

* Taking into account the JwtBearer options
(actually the authority) into the Microsoft
identity options if needed.

* Adding the Merged options generator to
the solution

* few more updates

Co-authored-by: Jean-Marc Prieur <jmprieur@microsoft.com>

* fix merge conflict

* clean-up, pr feedback

Co-authored-by: Jean-Marc Prieur <jmprieur@microsoft.com>
  • Loading branch information
jennyf19 and jmprieur authored May 19, 2021
1 parent b106d9a commit 6b506eb
Show file tree
Hide file tree
Showing 94 changed files with 41,853 additions and 2,682 deletions.
17 changes: 17 additions & 0 deletions Microsoft.Identity.Web.sln
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureFunctions", "AzureFunc
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleFunc", "tests\AzureFunctions\SampleFunc\SampleFunc.csproj", "{BE7E71AD-7272-4431-B50D-357C61CE2F8F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MultipleAuthSchemes", "MultipleAuthSchemes", "{D3A42D78-8A23-44EB-9A1F-63CF58B56AAC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mvcwebapp-graph", "tests\MultipleAuthSchemes\mvcwebapp-graph.csproj", "{15B7F1B3-A438-4A42-BFFA-E3382543C037}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerateMergeOptionsMethods", "tools\GenerateMergeOptionsMethods\GenerateMergeOptionsMethods.csproj", "{5B9AD363-6A27-4AF2-939E-B20E7C941ADF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -274,6 +280,14 @@ Global
{BE7E71AD-7272-4431-B50D-357C61CE2F8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE7E71AD-7272-4431-B50D-357C61CE2F8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE7E71AD-7272-4431-B50D-357C61CE2F8F}.Release|Any CPU.Build.0 = Release|Any CPU
{15B7F1B3-A438-4A42-BFFA-E3382543C037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15B7F1B3-A438-4A42-BFFA-E3382543C037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15B7F1B3-A438-4A42-BFFA-E3382543C037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15B7F1B3-A438-4A42-BFFA-E3382543C037}.Release|Any CPU.Build.0 = Release|Any CPU
{5B9AD363-6A27-4AF2-939E-B20E7C941ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5B9AD363-6A27-4AF2-939E-B20E7C941ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B9AD363-6A27-4AF2-939E-B20E7C941ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B9AD363-6A27-4AF2-939E-B20E7C941ADF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -325,6 +339,9 @@ Global
{9A51E6E0-A2B0-4D4D-9209-E73B8AD2B9C6} = {79310504-1334-4F14-93C4-1240913224BA}
{2A2EB8D3-88F2-4FA2-8DDB-56B1F4755475} = {9A51E6E0-A2B0-4D4D-9209-E73B8AD2B9C6}
{BE7E71AD-7272-4431-B50D-357C61CE2F8F} = {2A2EB8D3-88F2-4FA2-8DDB-56B1F4755475}
{D3A42D78-8A23-44EB-9A1F-63CF58B56AAC} = {9A51E6E0-A2B0-4D4D-9209-E73B8AD2B9C6}
{15B7F1B3-A438-4A42-BFFA-E3382543C037} = {D3A42D78-8A23-44EB-9A1F-63CF58B56AAC}
{5B9AD363-6A27-4AF2-939E-B20E7C941ADF} = {20CDB9B2-D237-413A-8E76-A973597748B0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F4FA8C4C-3251-41CC-939B-7892F5798549}
Expand Down
12 changes: 12 additions & 0 deletions src/Microsoft.Identity.Web.MicrosoftGraph/BaseRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ public static T WithAppOnly<T>(this T baseRequest, bool appOnly = true, string?
});
}

/// <summary>
/// Sets the authentication scheme that will be used by <see cref="IAuthenticationProvider"/> to authenticate this request.
/// This only works with the default authentication handler and default set of Microsoft Graph authentication providers.
/// If you use a custom authentication handler or authentication provider, you have to handle its retrieval in your implementation.
/// </summary>
/// <param name="baseRequest">The <see cref="IBaseRequest"/>.</param>
/// <param name="authenticationScheme">Authentication scheme used to authenticate this request.</param>
public static T WithAuthenticationScheme<T>(this T baseRequest, string authenticationScheme) where T : IBaseRequest
{
return SetParameter(baseRequest, options => options.AuthenticationScheme = authenticationScheme);
}

private static T SetParameter<T>(T baseRequest, Action<TokenAcquisitionAuthenticationProviderOption> action) where T : IBaseRequest
{
string authHandlerOptionKey = typeof(AuthenticationHandlerOption).ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ public async Task AuthenticateRequestAsync(HttpRequestMessage request)
var scopes = _initialOptions.Scopes;
bool appOnly = _initialOptions.AppOnly ?? false;
string? tenant = _initialOptions.Tenant ?? null;
string? scheme = _initialOptions.AuthenticationScheme ?? null;
// Extract per-request options from the request if present
TokenAcquisitionAuthenticationProviderOption? msalAuthProviderOption = GetMsalAuthProviderOption(request);
if (msalAuthProviderOption != null) {
scopes = msalAuthProviderOption.Scopes ?? scopes;
appOnly = msalAuthProviderOption.AppOnly ?? appOnly;
tenant = msalAuthProviderOption.Tenant ?? tenant;
scheme = msalAuthProviderOption.AuthenticationScheme ?? scheme;
}

if (!appOnly && scopes == null)
Expand All @@ -50,11 +52,11 @@ public async Task AuthenticateRequestAsync(HttpRequestMessage request)
string token;
if (appOnly)
{
token = await _tokenAcquisition.GetAccessTokenForAppAsync(Constants.DefaultGraphScope, tenant).ConfigureAwait(false);
token = await _tokenAcquisition.GetAccessTokenForAppAsync(Constants.DefaultGraphScope, tenant, authenticationScheme: scheme).ConfigureAwait(false);
}
else
{
token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes!).ConfigureAwait(false);
token = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes!, authenticationScheme: scheme).ConfigureAwait(false);
}

request.Headers.Authorization = new AuthenticationHeaderValue(Constants.Bearer, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ internal class TokenAcquisitionAuthenticationProviderOption : IAuthenticationPro
public string[]? Scopes { get; set; }
public bool? AppOnly { get; set; }
public string? Tenant { get; set; }

public string? AuthenticationScheme { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ namespace Microsoft.Identity.Web.UI.Areas.MicrosoftIdentity.Controllers
[Route("[area]/[controller]/[action]")]
public class AccountController : Controller
{
private readonly IOptions<MicrosoftIdentityOptions> _options;
private readonly IOptionsMonitor<MicrosoftIdentityOptions> _optionsMonitor;

/// <summary>
/// Constructor of <see cref="AccountController"/> from <see cref="MicrosoftIdentityOptions"/>
/// This constructor is used by dependency injection.
/// </summary>
/// <param name="microsoftIdentityOptions">Configuration options.</param>
public AccountController(IOptions<MicrosoftIdentityOptions> microsoftIdentityOptions)
/// <param name="microsoftIdentityOptionsMonitor">Configuration options.</param>
public AccountController(IOptionsMonitor<MicrosoftIdentityOptions> microsoftIdentityOptionsMonitor)
{
_options = microsoftIdentityOptions;
_optionsMonitor = microsoftIdentityOptionsMonitor;
}

/// <summary>
Expand All @@ -58,6 +58,7 @@ public IActionResult SignIn([FromRoute] string scheme)
/// <param name="domainHint">Domain hint.</param>
/// <param name="claims">Claims.</param>
/// <param name="policy">AAD B2C policy.</param>
/// <param name="scheme">Authentication scheme.</param>
/// <returns>Challenge generating a redirect to Azure AD to sign in the user.</returns>
[HttpGet("{scheme?}")]
public IActionResult Challenge(
Expand All @@ -66,9 +67,10 @@ public IActionResult Challenge(
string loginHint,
string domainHint,
string claims,
string policy)
string policy,
[FromRoute] string scheme)
{
string scheme = OpenIdConnectDefaults.AuthenticationScheme;
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
Dictionary<string, string?> items = new Dictionary<string, string?>
{
{ Constants.Claims, claims },
Expand Down Expand Up @@ -127,7 +129,7 @@ public IActionResult ResetPassword([FromRoute] string scheme)

var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Items[Constants.Policy] = _options.Value?.ResetPasswordPolicyId;
properties.Items[Constants.Policy] = _optionsMonitor.Get(scheme).ResetPasswordPolicyId;
return Challenge(properties, scheme);
}

Expand All @@ -148,7 +150,7 @@ public async Task<IActionResult> EditProfile([FromRoute] string scheme)

var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Items[Constants.Policy] = _options.Value?.EditProfilePolicyId;
properties.Items[Constants.Policy] = _optionsMonitor.Get(scheme).EditProfilePolicyId;
return Challenge(properties, scheme);
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/Microsoft.Identity.Web.UI/Microsoft.Identity.Web.UI.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private IConfidentialClientApplication GetOrCreateApplication()
public async Task<string> GetAccessTokenForAppAsync(
string scope,
string? tenant = null,
TokenAcquisitionOptions? tokenAcquisitionOptions = null)
TokenAcquisitionOptions? tokenAcquisitionOptions = null,
string? authenticationScheme = null)
{
// We could use MSI
if (scope is null)
Expand All @@ -125,7 +126,8 @@ public Task<string> GetAccessTokenForUserAsync(
string? tenantId = null,
string? userFlow = null,
ClaimsPrincipal? user = null,
TokenAcquisitionOptions? tokenAcquisitionOptions = null)
TokenAcquisitionOptions? tokenAcquisitionOptions = null,
string? authenticationScheme = null)
{
var httpContext = CurrentHttpContext;
string accessToken;
Expand Down Expand Up @@ -174,7 +176,8 @@ public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
string? tenantId = null,
string? userFlow = null,
ClaimsPrincipal? user = null,
TokenAcquisitionOptions? tokenAcquisitionOptions = null)
TokenAcquisitionOptions? tokenAcquisitionOptions = null,
string? authenticationScheme = null)
{
string? idToken = AppServicesAuthenticationInformation.GetIdToken(CurrentHttpContext?.Request?.Headers!);
ClaimsPrincipal? userClaims = AppServicesAuthenticationInformation.GetUser(CurrentHttpContext?.Request?.Headers!);
Expand Down Expand Up @@ -214,21 +217,38 @@ public async Task<AuthenticationResult> GetAuthenticationResultForUserAsync(
}

/// <inheritdoc/>
public Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(IEnumerable<string> scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null)
public Task ReplyForbiddenWithWwwAuthenticateHeaderAsync(
IEnumerable<string> scopes,
MsalUiRequiredException msalServiceException,
HttpResponse? httpResponse = null)
{
// Not implemented for the moment
throw new NotImplementedException();
}

/// <inheritdoc/>
public void ReplyForbiddenWithWwwAuthenticateHeader(IEnumerable<string> scopes, MsalUiRequiredException msalServiceException, HttpResponse? httpResponse = null)
public void ReplyForbiddenWithWwwAuthenticateHeader(
IEnumerable<string> scopes,
MsalUiRequiredException msalServiceException,
HttpResponse? httpResponse = null,
string? authenticationScheme = null)
{
// Not implemented for the moment
throw new NotImplementedException();
}

/// <inheritdoc/>
public Task<AuthenticationResult> GetAuthenticationResultForAppAsync(string scope, string? tenant = null, TokenAcquisitionOptions? tokenAcquisitionOptions = null)
public Task<AuthenticationResult> GetAuthenticationResultForAppAsync(
string scope,
string? tenant = null,
TokenAcquisitionOptions? tokenAcquisitionOptions = null,
string? authenticationScheme = null)
{
throw new NotImplementedException();
}

/// <inheritdoc/>
public string GetEffectiveAuthenticationScheme(string? authenticationScheme)
{
throw new NotImplementedException();
}
Expand Down
Loading

0 comments on commit 6b506eb

Please sign in to comment.