Skip to content
This repository has been archived by the owner on Dec 13, 2018. It is now read-only.

Handle authorize and forbid for redirecting handlers. #823

Merged
merged 1 commit into from
May 23, 2016
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 40 additions & 6 deletions samples/OpenIdConnectSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -81,25 +82,58 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<html><body>Signing out {context.User.Identity.Name}<br>{Environment.NewLine}");
await context.Response.WriteAsync($"<html><body>Signed out {context.User.Identity.Name}<br>{Environment.NewLine}");
await context.Response.WriteAsync("<a href=\"/\">Sign In</a>");
await context.Response.WriteAsync($"</body></html>");
return;
}
if (context.Request.Path.Equals("/Account/AccessDenied"))
{
await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<html><body>Access Denied for user {context.User.Identity.Name} to resource '{context.Request.Query["ReturnUrl"]}'<br>{Environment.NewLine}");
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a>");
await context.Response.WriteAsync($"</body></html>");
return;
}

// CookieAuthenticationOptions.AutomaticAuthenticate = true (default) causes User to be set
var user = context.User;

// This is what [Authorize] calls
// var user = await context.Authentication.AuthenticateAsync(AuthenticationManager.AutomaticScheme);

// This is what [Authorize(ActiveAuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] calls
// var user = await context.Authentication.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);

// Not authenticated
if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
{
// This is what [Authorize] calls
// The cookie middleware will intercept this 401 and redirect to /login
await context.Authentication.ChallengeAsync();

// This is what [Authorize(ActiveAuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme)] calls
// await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme);

return;
}

if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
// Authenticated, but not authorized
if (context.Request.Path.Equals("/restricted") && !user.Identities.Any(identity => identity.HasClaim("special", "true")))
{
await context.Authentication.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" });
await context.Authentication.ChallengeAsync();
return;
}

context.Response.ContentType = "text/html";
await context.Response.WriteAsync($"<html><body>Hello Authenticated User {context.User.Identity.Name}<br>{Environment.NewLine}");
foreach (var claim in context.User.Claims)
await context.Response.WriteAsync($"<html><body>Hello Authenticated User {user.Identity.Name}<br>{Environment.NewLine}");
foreach (var claim in user.Claims)
{
await context.Response.WriteAsync($"{claim.Type}: {claim.Value}<br>{Environment.NewLine}");
}
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a>");
await context.Response.WriteAsync("<a href=\"/restricted\">Restricted</a><br>");
await context.Response.WriteAsync("<a href=\"/signout\">Sign Out</a><br>");
await context.Response.WriteAsync($"</body></html>");
});
}
Expand Down
4 changes: 2 additions & 2 deletions samples/SocialSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
"SocialSample": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "https://localhost:54541/",
"launchUrl": "https://localhost:44318/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_SERVER.URLS": "https://localhost:54541/"
"ASPNETCORE_SERVER.URLS": "https://localhost:44318/"
}
}
}
Expand Down
29 changes: 20 additions & 9 deletions samples/SocialSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,21 +313,32 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
});
});

// Deny anonymous request beyond this point.
app.Use(async (context, next) =>

app.Run(async context =>
{
if (!context.User.Identities.Any(identity => identity.IsAuthenticated))
// CookieAuthenticationOptions.AutomaticAuthenticate = true (default) causes User to be set
var user = context.User;

// This is what [Authorize] calls
// var user = await context.Authentication.AuthenticateAsync(AuthenticationManager.AutomaticScheme);

// This is what [Authorize(ActiveAuthenticationSchemes = MicrosoftAccountDefaults.AuthenticationScheme)] calls
// var user = await context.Authentication.AuthenticateAsync(MicrosoftAccountDefaults.AuthenticationScheme);

// Deny anonymous request beyond this point.
if (user == null || !user.Identities.Any(identity => identity.IsAuthenticated))
{
// This is what [Authorize] calls
// The cookie middleware will intercept this 401 and redirect to /login
await context.Authentication.ChallengeAsync();

// This is what [Authorize(ActiveAuthenticationSchemes = MicrosoftAccountDefaults.AuthenticationScheme)] calls
// await context.Authentication.ChallengeAsync(MicrosoftAccountDefaults.AuthenticationScheme);

return;
}
await next();
});

// Display user information
app.Run(async context =>
{
// Display user information
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("Hello " + (context.User.Identity.Name ?? "anonymous") + "<br>");
Expand All @@ -342,7 +353,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
await context.Response.WriteAsync("Refresh Token: " + await context.Authentication.GetTokenAsync("refresh_token") + "<br>");
await context.Response.WriteAsync("Token Type: " + await context.Authentication.GetTokenAsync("token_type") + "<br>");
await context.Response.WriteAsync("expires_at: " + await context.Authentication.GetTokenAsync("expires_at") + "<br>");
await context.Response.WriteAsync("<a href=\"/logout\">Logout</a>");
await context.Response.WriteAsync("<a href=\"/logout\">Logout</a><br>");
await context.Response.WriteAsync("</body></html>");
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,32 @@ protected virtual async Task<bool> HandleRemoteCallbackAsync()

protected abstract Task<AuthenticateResult> HandleRemoteAuthenticateAsync();

protected override Task<AuthenticateResult> HandleAuthenticateAsync()
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Remote authentication does not support authenticate"));
// Most RemoteAuthenticationHandlers will have a PriorHandler, but it might not be set up during unit tests.
if (PriorHandler != null)
{
var authenticateContext = new AuthenticateContext(Options.SignInScheme);
await PriorHandler.AuthenticateAsync(authenticateContext);
if (authenticateContext.Accepted)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So asking for Facebook might return a Google ticket if the two middleware share the same cookies middleware for identity persistence? 🔫

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well given that previously asking for Facebook would never give you any identity anyways, yes... now remote schemes will give you the 'sign in scheme' identity when you ask. If you are sharing sign in schemes, that may give you someone else's identity... Kind of weird but I think it makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I am talking mostly about the 'external' public API call of Authenticate("Facebook") making sense, it is true what you say that if direct calls to HandleAuthenticateAsync will show the much weirder behavior of getting a "Google" ticket back potentially.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well given that previously asking for Facebook would never give you any identity anyways, yes...

IMHO, that was a much more reasonable behavior. Now, HandleAuthenticateAsync will just lie hard.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PinpointTownes Did you see my prior version that tried to verify the provider?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, here was my remark:

IMHO, this fix seems fragile as it relies on the informational ClaimsIssuer property. If you really like this approach, I think it would be preferable to bind ClaimsIdentity.AuthenticationType with AuthenticationScheme.

That said, it's probably better than no check at all... 😨

{
if (authenticateContext.Error != null)
{
return AuthenticateResult.Fail(authenticateContext.Error);
}

if (authenticateContext.Principal != null)
{
return AuthenticateResult.Success(new AuthenticationTicket(authenticateContext.Principal,
new AuthenticationProperties(authenticateContext.Properties), Options.AuthenticationScheme));
}

return AuthenticateResult.Fail("Not authenticated");
}

}

return AuthenticateResult.Fail("Remote authentication does not support authenticate");
}

protected override Task HandleSignOutAsync(SignOutContext context)
Expand All @@ -104,9 +127,11 @@ protected override Task HandleSignInAsync(SignInContext context)
throw new NotSupportedException();
}

protected override Task<bool> HandleForbiddenAsync(ChallengeContext context)
protected override async Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
throw new NotSupportedException();
var challengeContext = new ChallengeContext(Options.SignInScheme, context.Properties, ChallengeBehavior.Forbidden);
await PriorHandler.ChallengeAsync(challengeContext);
return challengeContext.Accepted;
}

protected virtual void GenerateCorrelationId(AuthenticationProperties properties)
Expand Down