Skip to content

Fixing merge #300

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 19, 2020
Merged
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 @@ -3,6 +3,11 @@
{
<ul class="nav navbar-nav navbar-right">
<li class="navbar-text">Hello @User.GetDisplayName()!</li>
<li class="navbar-btn">
<form method="get" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="EditProfile">
<button type="submit" class="btn btn-primary" style="margin-right:5px">Edit Profile</button>
</form>
</li>
<li><a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut">Sign out</a></li>
</ul>
}
Expand Down
11 changes: 0 additions & 11 deletions 4-WebApp-your-API/4-2-B2C/Client/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@
"ClientSecret": "X330F3#92!z614M4"
//"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc",

// To call an API
"ClientSecret": "[Copy the client secret added to the app from the Azure portal]"
},
"TodoList": {
/*
TodoListScope is the scope of the Web API you want to call. This can be: "api://8f085429-c424-45c4-beb3-75f6f0a7924f/user_impersonation",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.Identity.Web;
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace TodoListService.AuthorizationPolicies
{
/// <summary>
/// Requirement used in authorization policies, to check if the scope claim has at least one of the requirement values.
/// Since the class also extends AuthorizationHandler, its dependency injection is done out of the box.
/// </summary>
public class ScopesRequirement : AuthorizationHandler<ScopesRequirement>, IAuthorizationRequirement
{
string[] _acceptedScopes;

public ScopesRequirement(params string[] acceptedScopes)
{
_acceptedScopes = acceptedScopes;
}

/// <summary>
/// AuthorizationHandler that will check if the scope claim has at least one of the requirement values
/// </summary>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ScopesRequirement requirement)
{
// If there are no scopes, do not process
if (!context.User.Claims.Any(x => x.Type == ClaimConstants.Scope)
&& !context.User.Claims.Any(y => y.Type == ClaimConstants.Scp))
{
return Task.CompletedTask;
}

Claim scopeClaim = context?.User?.FindFirst(ClaimConstants.Scp);

if (scopeClaim == null)
scopeClaim = context?.User?.FindFirst(ClaimConstants.Scope);

if (scopeClaim != null && scopeClaim.Value.Split(' ').Intersect(requirement._acceptedScopes).Any())
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public TodoListController(IHttpContextAccessor contextAccessor)

// GET: api/values
[HttpGet]
[Authorize(Policy = "ReadScope")]
public IEnumerable<Todo> Get()
{
string owner = User.Identity.Name;
Expand All @@ -62,6 +63,7 @@ public IEnumerable<Todo> Get()

// GET: api/values
[HttpGet("{id}", Name = "Get")]
[Authorize(Policy = "ReadScope")]
public Todo Get(int id)
{
return TodoStore.Values.FirstOrDefault(t => t.Id == id);
Expand Down
16 changes: 15 additions & 1 deletion 4-WebApp-your-API/4-2-B2C/TodoListService/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Web;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using TodoListService.AuthorizationPolicies;
using Microsoft.AspNetCore.Authorization.Infrastructure;

namespace TodoListService
{
Expand All @@ -31,9 +34,20 @@ public void ConfigureServices(IServiceCollection services)

// Adds Microsoft Identity platform (AAD v2.0) support to protect this Api
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddProtectedWebApi("AzureAdB2C", Configuration, options => Configuration.Bind("AzureAdB2C", options));
.AddProtectedWebApi("AzureAdB2C", Configuration, options =>
{
Configuration.Bind("AzureAdB2C", options);

options.TokenValidationParameters.NameClaimType = "name";
});

services.AddControllers();
services.AddAuthorization(options =>
{
// Create policy to check for the scope 'read'
options.AddPolicy("ReadScope",
policy => policy.Requirements.Add(new ScopesRequirement("read")));
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand Down
7 changes: 0 additions & 7 deletions 4-WebApp-your-API/4-2-B2C/TodoListService/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@
"EditProfilePolicyId": "b2c_1_edit_profile" // Optional profile editing policy
//"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc
},
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[Enter the domain of your tenant, e.g. contoso.onmicrosoft.com]",
"TenantId": "[Enter 'common', or 'organizations' or the Tenant Id (Obtained from the Azure portal. Select 'Endpoints' from the 'App registrations' blade and use the GUID in any of the URLs), e.g. da41245a5-11b3-996c-00a8-4d99re19f292]",
"ClientId": "[Enter the Client Id (Application ID obtained from the Azure portal), e.g. ba74781c2-53c2-442a-97c2-3d60re42f403]"

},
"Kestrel": {
"Endpoints": {
"Http": {
Expand Down
12 changes: 5 additions & 7 deletions Microsoft.Identity.Web/AuthorizeForScopesAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class AuthorizeForScopesAttribute : ExceptionFilterAttribute
/// <param name="context">Context provided by ASP.NET Core</param>
public override void OnException(ExceptionContext context)
{
// Do not re-use the attribute param Scopes. For more info: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273
string[] incrementalConsentScopes = new string[] { };
MsalUiRequiredException msalUiRequiredException = context.Exception as MsalUiRequiredException;

Expand All @@ -57,9 +58,6 @@ public override void OnException(ExceptionContext context)
{
if (CanBeSolvedByReSignInOfUser(msalUiRequiredException))
{
// Do not re-use the attribute param Scopes. For more info: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/273
string[] scopes = null;

// the users cannot provide both scopes and ScopeKeySection at the same time
if (!string.IsNullOrWhiteSpace(ScopeKeySection) && Scopes != null && Scopes.Length > 0)
{
Expand All @@ -77,17 +75,17 @@ public override void OnException(ExceptionContext context)
throw new InvalidOperationException($"The {nameof(ScopeKeySection)} is provided but the IConfiguration instance is not present in the services collection");
}

scopes = new string[] { configuration.GetValue<string>(ScopeKeySection) };
incrementalConsentScopes = new string[] { configuration.GetValue<string>(ScopeKeySection) };

if (Scopes != null && Scopes.Length > 0 && scopes != null && scopes.Length > 0)
if (Scopes != null && Scopes.Length > 0 && incrementalConsentScopes != null && incrementalConsentScopes.Length > 0)
{
throw new InvalidOperationException("no scopes provided in scopes...");
}
}
else
scopes = Scopes;
incrementalConsentScopes = Scopes;

var properties = BuildAuthenticationPropertiesForIncrementalConsent(scopes, msalUiRequiredException, context.HttpContext);
var properties = BuildAuthenticationPropertiesForIncrementalConsent(incrementalConsentScopes, msalUiRequiredException, context.HttpContext);
context.Result = new ChallengeResult(properties);
}
}
Expand Down
1 change: 1 addition & 0 deletions Microsoft.Identity.Web/ClaimConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static class ClaimConstants
// Newer scope claim
public const string Scp = "scp";
public const string Roles = "roles";
public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
public const string Sub = "sub";

// Policy claims
Expand Down
3 changes: 2 additions & 1 deletion Microsoft.Identity.Web/WebApiServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ public static AuthenticationBuilder AddProtectedWebApi(
// This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope)
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp)
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles))
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)
&& !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Role))
{
throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
}
Expand Down
45 changes: 0 additions & 45 deletions Microsoft.Identity.Web/WebAppServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,50 +308,5 @@ public static AuthenticationBuilder AddSignIn(

return builder;
}


// Method taken from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/
public static bool DisallowsSameSiteNone(string userAgent)
{
if (string.IsNullOrEmpty(userAgent))
{
return false;
}

// Cover all iOS based browsers here. This includes:
// - Safari on iOS 12 for iPhone, iPod Touch, iPad
// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
// - Chrome on iOS 12 for iPhone, iPod Touch, iPad
// All of which are broken by SameSite=None, because they use the iOS networking
// stack.
if (userAgent.Contains("CPU iPhone OS 12") ||
userAgent.Contains("iPad; CPU OS 12"))
{
return true;
}

// Cover Mac OS X based browsers that use the Mac OS networking stack.
// This includes:
// - Safari on Mac OS X.
// This does not include:
// - Chrome on Mac OS X
// Because they do not use the Mac OS networking stack.
if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
userAgent.Contains("Version/") && userAgent.Contains("Safari"))
{
return true;
}

// Cover Chrome 50-69, because some versions are broken by SameSite=None,
// and none in this range require it.
// Note: this covers some pre-Chromium Edge versions,
// but pre-Chromium Edge does not require SameSite=None.
if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
{
return true;
}

return false;
}
}
}