I'm unable to log off the user in an MVC 5 app using ASP.NET Identity with Owin (latest versions). Login works great...but I can't log the user off without opening the browser settings to delete the cookie.
When the LogOff action runs, the browser redirects to the designated page which has the [Authorize] attribute. It should be rejected at that point and redirected to the Login page.
Note that if I manually delete the cookies, it will redirect correctly when attempting to open the [Authorize] page, so the redirect action for unauthenticated users is working correctly.
I see a lot of similar questions and have tried the solutions, but nothing is working so far.
I changed:
AuthenticationManager.SignOut();
To:
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
as suggested in previous answers, but it didn't change the behavior.
Login works fine. I notice after attempting to LogOff, there are two cookies with the same name instead of just one. One cookie is empty, and one is not.
Here's my LogOff method:
[HttpPost]
[AllowAnonymous]
public ActionResult LogOff()
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
//Clear the principal to ensure the user does not retain any authentication
HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
// Redirect to a controller/action that requires authentication to ensure a redirect takes place
// this clears the Request.IsAuthenticated flag since this triggers a new request
return RedirectToLocal(String.Empty);
}
And my OwinStartup class:
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>(new TenantDbContext()));
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(TenantDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieSecure = CookieSecureOption.Always,
Provider = new CookieAuthProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
}
public class CookieAuthProvider : CookieAuthenticationProvider
{
public override void ResponseSignIn(CookieResponseSignInContext context)
{
context.CookieOptions.Domain = context.Request.Uri.Host;
base.ResponseSignIn(context);
}
}
}
And here's my AuthenticationManager:
private IAuthenticationManager AuthenticationManager
{
get
{
return HttpContext.GetOwinContext().Authentication;
}
}
Related
I've set up 2 factor authentication in my .net core mvc application using the guide from here
This is all working fine however it relies on the user going into their account and setting up 2FA. Is there any way I can force the user to do this so all users must use 2FA?
I'm a bit late. But the way to do it is by making a custom Controller and overriding the OnActionExecuting method.
public class CustomController : Controller
{
public readonly DataContext _db;
public CustomController(DataContext context)
{
_db = context;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (User.Identity.IsAuthenticated)
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
UserModel user = _db.UserModel.Find(userId);
if (user.TwoFactorEnabled==false && ControllerContext.ActionDescriptor.ActionName != "TwoFactor")
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "TwoFactor" }));
return;
}
}
base.OnActionExecuting(filterContext);
}
}
One ways is during login you check whether user have set the 2FA by :
var user = await _userManager.FindByEmailAsync(Input.Email);
var twoFactorEnabled = user.TwoFactorEnabled;
If it is false , you can then redirect user to config the 2FA page(./Manage/TwoFactorAuthentication) , after user set the 2FA and enable 2FA , TwoFactorEnabled in AspNetUsers table will be True and then during the login process , identity will automatically redirect user to ./LoginWith2fa page for 2FA login if current user's TwoFactorEnabled value is true .
I'm building an Azure AD authentication which works fine, the only issue is that if a user clicks on a link that looks like host.com/xxx/bbb if they're not authenticated then they get redirected to root.
I need them to still be redirected to the original URL they entered in the browser. Can can this be achieved? Below is a snippet of the code I use in app startup:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = XXX,
Authority = string.Format("https://login.microsoftonline.com/{0}", YYY,
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async n => { await Task.Run(() => SetRedirectUrl(n)); }
}
});
}
private static void SetRedirectUrl(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.ProtocolMessage.RedirectUri = notification.Request.Uri.AbsoluteUri;
}
None of the properties of OwinRequest contain a full path I'm looking for. I've also tried looking at
HttpContext.Current.Request.Url
but this also does not have the full address.
I need them to still be redirected to the original URL they entered in
the browser
You need to set the SignIn() method in AccountController.cs. You could use “Request.UrlReferrer.ToString();” to get the entered url:
public void SignIn()
{
// var host = Request.UserHostName.ToString();
var ori = Request.UrlReferrer.ToString();
var index = ori.LastIndexOf('/');
var action = ori.Substring(index);
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = action },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
I need to authenticate my users using an external API from the login page. If the authentication from the external API succeed then I store at the session a AuthToken.
To check if the request is valid I have created the following Authorization Handler
public class ExtApiStoreRequirement : IAuthorizationRequirement
{
}
public class ExtApiAuthorizationHandler : AuthorizationHandler<ExtApiStoreRequirement>
{
IHttpContextAccessor _accessor;
public ExtApiAuthorizationHandler(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ExtApiStoreRequirement requirement)
{
var authState = GET_AUTH_FROM_SESSION(_accessor.HttpContext.Session);
if (authState!=null)
{
_accessor.HttpContext.Response.Redirect("/Account/Login");
//context.Fail(); <-- I removed that because it was responding an empty page
context.Succeed(requirement);
}
else
context.Succeed(requirement);
return Task.CompletedTask;
}
}
And I have registered this handler at my startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("ExtApi",
policy => policy.Requirements.Add(new ExtApiStoreRequirement()));
});
This approach is working but I don't feel confident because I have to call context.Succeed(requirement); for the redirection to work. If I call context.Fail() then no redirection takes place and all I see is an empty page.
Is there any security issue with this approach or I will be safe using it?
Your implementation is for authorization not authentication. I think instead of creating an authorization policy, writing custom authentication middleware would be right way for your case.
First see how to implement custom authentication Simple token based authentication/authorization in asp.net core for Mongodb datastore
To implement above way for your case HandleAuthenticateAsync should be something like below:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
AuthenticateResult result = null;
var principal = GetPrincipalFromSession();
if(principal != null)
{
result = AuthenticateResult.Success(new AuthenticationTicket(principal,
new AuthenticationProperties(), Options.AuthenticationScheme));
}
else
{
result = AuthenticateResult.Skip();
}
return result;
}
Update based on comment:
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
Response.Redirect(Options.LoginPath);// you need to define LoginPath
return true;
}
Also you should store principal in session when user signs in.
I've reviewed similar posts with solutions that do not work for me.
I have an MVC 5 site hosted in IIS 7.x that serves a web ui - https://www.example.com. Callers can also access api (Webapi 2.2) endpoints to perform certain functions - https://www.example.com/api/x. Some pages/apis are secured while others are not. The mvc/web ui security is managed by owin middleware configured with UseCookieAuthentication and UseWsFederationAuthentication.
The secured pages in the webui are automatically redirected to an ADFS login screen when the user does not have already have a valid SAML token - as desired.
The secured web apis require a separate JWT token passed in the Auth header.
The Webapi is hosted in the same app pool as MVC. The Webapi does NOT have controllers, instead the webapiconfig has routes that leverage a DelegatingHandler to route/pass through the api calls. The Delegating handler is the one that checks to see if the JWT is included in the Auth header and if so allows it to continue to a different internal webapi that validates the JWT. If the JWT is not present then the DelegatingHandler returns a 401.
The 401 return used to work as it shortcircuited a continuation of the request and therefore bypassed any owin pipeline stuff. However, now when the shortcircuit fires the 401 is not returned. Instead the request continues and gets passes onto the Owin auth which then redirects (302) to the ADFS login. I have no idea why. If I change the response status code to something other than 401 then Owin Auth ignores it.
Please see the code below:
Global.asax.cs
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Apis",
routeTemplate: "api/{*path}",
handler: HttpClientFactory.CreatePipeline
(
innerHandler: new HttpClientHandler(),
handlers: new DelegatingHandler[] { new ApiHandler() }
),
defaults: new { path = RouteParameter.Optional },
constraints: null
);
}
}
ApiHandler.cs
internal class ApiHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
try
{
// get jwt from header
var jwt = GetJWTFromHeader(request.Headers);
if (jwt == null)
{
response.ReasonPhrase = "Token required";
return await Task.FromResult<HttpResponseMessage>(response);
}
else if (!IsValidJWT(jwt))
{
response.ReasonPhrase = "Invalid token";
return await Task.FromResult<HttpResponseMessage>(response);
}
response = await base.SendAsync(request, cancellationToken);
}
catch (Exception ex)
{
// log error
response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
// return result
return response;
}
}
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(
new CookieAuthenticationOptions()
{
SlidingExpiration = false
}
);
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = ADFS_REALM,
MetadataAddress = ADFS_METADATA,
UseTokenLifetime = true,
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
},
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = async r =>
{
// do stuff
},
SecurityTokenValidated = async s =>
{
// if we get here, then UI user has valid saml token
// do stuff
}
}
}
});
}
I appreciate any help. Please let me know if more details are needed!
Looks like you can use:
https://msdn.microsoft.com/en-us/library/system.web.http.owinhttpconfigurationextensions.suppressdefaulthostauthentication(v=vs.118).aspx
config.SuppressDefaultHostAuthentication();
Thanks to Finallz I was able to refine my search and come across an answer - found here. In my case, I don't need any special authentication config since I'm manually inspected the JWT in the apihandler. However, by simply including a map to my api path, it naturally overrides the Owin security:
app.Map("/api", inner =>
{
// nothing to do here since we don't have any concrete controllers to manage special authorization for
// we're using apihandlers to pass api traffic through to next stop
});
I am authenticating users via GoogleOpenIdOAuthProvider. I need to access the email address of the user that logged in. I have attempted to implement the Using Typed Sessions in ServiceStack code as-is.
So, I created a base class that my service inherits from:
public abstract class AppServiceBase : Service
{
//private CustomUserSession userSession;
protected CustomUserSession UserSession
{
get
{
return base.SessionAs<CustomUserSession>();
}
}
}
public class CustomUserSession : AuthUserSession
{
public string CustomId { get; set; }
}
The service has the [Authenticate] attribute on it. In my AppHost setup, I have configured auth like this:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new GoogleOpenIdOAuthProvider(appSettings) //Sign-in with Google OpenId
}));
Once the user has authenticated, the service tries to access the auth session from the base class like this:
var x = base.UserSession.Email;
However, Email is always null. How can I access this value?
You will need to pull the data from the AuthProvider and set the value in the CustomUserSession. An example of this is shown in the SocialBootstrapApi sample
https://github.com/ServiceStack/SocialBootstrapApi/blob/master/src/SocialBootstrapApi/Models/CustomUserSession.cs#L50
Override OnAuthenticated, find the GoogleOpenIdOAuthProvider to get to the email address.
Another example is shown at ServiceStack OAuth - registration instead login