Reputation: 34149
I have ASP.NET Core application that is using OpenIDConnect authentication. In OnTokenValidated
event i check if Authenticated
user exists in the my application's database, and if not then i am throwing UnauthorizedAccessException
Note that in OnTokenValidated
event i am creating new identity if user exists in the application's DB, else i leave authenticated user as it is.
public class Startup
{
private readonly ILogger<Startup> _logger;
public IConfiguration Configuration { get; }
private IHostingEnvironment _environment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment env, ILogger<Startup> logger)
{
Configuration = configuration;
_environment = env;
_logger = logger;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/Home";
options.AccessDeniedPath = "/Account/Forbidden";
options.Cookie = new CookieBuilder()
{
Name = "myCookie",
HttpOnly = true,
};
options.SlidingExpiration = true;
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
//options.ForwardChallenge
options.Authority = configuration["IdentityOptions:Authority"];
options.ClientId = configuration["IdentityOptions:ClientID"];
options.ResponseType = "id_token";
options.CallbackPath = "/Home";
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; //tells cookies scheme to persist user's identity in cookie.
options.SignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;//tells cookies scheme to remove persisted cookie
options.Scope.Add(OpenIdConnectScope.Email);
options.Events = new OpenIdConnectEvents()
{
OnTokenValidated = async context =>
{
var emailClaim = context.Principal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Email);
// check if user exists in client applications db
CompanyUser cu = null;
using (var serviceProvider = services.BuildServiceProvider())
{
using (var serviceScope = serviceProvider.CreateScope())
{
using (var accountService = serviceScope.ServiceProvider.GetService<IAccountService>())
{
cu = await accountService.Authorize(emailClaim.Value);
}
}
}
if (cu == null)
{
// context.Principal.Identity.IsAuthenticated is true here
throw new UnauthorizedAccessException(string.Format("Could not find user for login '{0}' ", emailClaim.Value));
}
//We will create new identity to store only required claims.
var newIdentity = new ClaimsIdentity(context.Principal.Identity.AuthenticationType);
// keep the id_token for logout
newIdentity.AddClaim(new Claim(IdentityClaimTypes.IdToken, context.ProtocolMessage.IdToken));
// add email claim
newIdentity.AddClaim(emailClaim);
context.Properties.IsPersistent = true;
context.Properties.ExpiresUtc = DateTime.UtcNow.AddHours(3);
// overwrite existing authentication ticket
context.Principal = new ClaimsPrincipal(newIdentity);
},
OnRedirectToIdentityProviderForSignOut = async context =>
{
var idTokenHint = context.HttpContext?.User?.FindFirst("id_token");
if (idTokenHint != null)
context.ProtocolMessage.IdTokenHint = idTokenHint.Value;
await Task.FromResult(0);
},
OnRemoteFailure = async context =>
{
//WHY context.HttpContext.User.Identity.IsAuthenticated is false here??
if (context.Failure is UnauthorizedAccessException)
{
context.Response.Redirect("/Account/AccessDenied");
}
else
{
context.Response.Redirect("/Account/Error");
}
context.HandleResponse();
await Task.FromResult(0);
}
};
});
}
}
Questions
1> In OnRemoteFailure
How do i auto logout/clear unauthorized
user BEFORE redirecting to AccessDenied view? AccessDenied
view is accessible to anonymous user.
I have tried
OnRemoteFailure = async context =>
{
if (context.Failure is UnauthorizedAccessException)
{
await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.SignOutAsync(context.HttpContext, CookieAuthenticationDefaults.AuthenticationScheme);
await Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.SignOutAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme);
context.Response.Redirect("/Account/AccessDenied");
}
else
{
context.Response.Redirect("/Account/Error");
}
context.HandleResponse();
await Task.FromResult(0);
}
However in OnTokenValidated
event context.HttpContext.User.Identity.IsAuthenticated
is false so it didn't work.
Upvotes: 1
Views: 3852
Reputation: 19971
A common mistake is to do a Reponse.Redirect after the Signout method calls, that sounds obvious. But that does unfortunately not work. The Signout methods creates their own redirect responses and when you do your redirect you "override" these redirects
The proper way to do implement this is to not return anything and let the Signout methods handle the redirect:
public async Task DoLogout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
}
To redirect to an alternative Url, you can try:
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties( )
{
RedirectUri = "alternativeUrl"
});
Upvotes: 3