I know this question might sound silly, but this problem is really making a big trouble.
I have a web application made on ASP.NET MVC 4. To enter that application one has to register, after that when the user logs out. It shows the proper message of logging out. But, if the user clicks the back button of the browser, It enters back to the user's profile.(It never happens every time, it happens very rarely).
Any type of suggestion is highly anticipated.
If i am not clear enough.Feel free to ask any question regarding this matter.
Please help me.
The credentials gets stored in cookie. I want to store that in cookie, but i want to remove that while logging out.
LogOut Code:
public ActionResult Logout()
{
Session.Clear();
if (HttpContext.Request.Cookies["xxxx"] != null)
{
var c = HttpContext.Request.Cookies["xxxx"];
c.Expires = DateTime.Now.AddDays(-10);
HttpContext.Response.Cookies.Add(c);
}
if (HttpContext.Request.Cookies["xxxx"] != null)
{
var c = HttpContext.Request.Cookies["xxxx"];
c.Expires = DateTime.Now.AddDays(-10);
HttpContext.Response.Cookies.Add(c);
}
HttpContext.Session["var1"] = null;
HttpContext.Session["var2"] = null;
HttpContext.Session["var3"] = null;
}
Related
In an asp.net core 3.1 web app with cookie-based authorization I have created a custom validator which executes on the cookie authorization's OnValidatePrincipal event. The validator does a few things, one of those is check in the backend if the user has been blocked. If the user has been blocked, The CookieValidatePrincipalContext.RejectPrincipal() method is executed and the user is signed out using the CookieValidatePrincipalContext.HttpContext.SignOutAsyn(...) method, as per the MS docs.
Here is the relevant code for the validator:
public static async Task ValidateAsync(CookieValidatePrincipalContext cookieValidatePrincipalContext)
{
var userPrincipal = cookieValidatePrincipalContext.Principal;
var userService = cookieValidatePrincipalContext.GetUserService();
var databaseUser = await userService.GetUserBySidAsync(userPrincipal.GetSidAsByteArray());
if (IsUserInvalidOrBlocked(databaseUser))
{
await RejectUser(cookieValidatePrincipalContext);
return;
}
else if (IsUserPrincipalOutdated(userPrincipal, databaseUser))
{
var updatedUserPrincipal = await CreateUpdatedUserPrincipal(userPrincipal, userService);
cookieValidatePrincipalContext.ReplacePrincipal(updatedUserPrincipal);
cookieValidatePrincipalContext.ShouldRenew = true;
}
}
private static bool IsUserInvalidOrBlocked(User user)
=> user is null || user.IsBlocked;
private static async Task RejectUser(CookieValidatePrincipalContext context)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
And here is the setup for the cookie-based authorization:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(co =>
{
co.LoginPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Login)}";
co.LogoutPath = #$"/{ControllerHelpers.GetControllerName<AuthenticationController>()}/{nameof(AuthenticationController.Logout)}";
co.ExpireTimeSpan = TimeSpan.FromDays(30);
co.Cookie.SameSite = SameSiteMode.Strict;
co.Cookie.Name = "GioBQADashboard";
co.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = UserPrincipalValidator.ValidateAsync
};
co.Validate();
});
This actually gets called and executed as expected and redirects the user to the login page when they navigate to a new page after having been blocked.
Most of the views have ajax calls to api methods that execute on a timer every 10 seconds. For those calls the credentials also get validated and the user gets signed out. However, after the user has been signed out, a popup asking for user credentials appears on the page:
If the user doesn't enter their credentials and navigate to another page, they get taken to the login page as expected.
If they do enter their credentials, they stay logged in, but their identity appears to be their windows identity...
What is going on here? What I would really want to achieve is for users to be taken to the login page for any request made after they have been signed out.
I have obviously misconfigured something, so that the cookie-based authorization doesn't work properly for ajax requests, but I cannot figure out what it is.
Or is it the Authorization attribute which does not work the way I'm expecting it to?
The code lines look good to me.
This login dialog seems to be the default one for Windows Authentication. Usually, it comes from the iisSettings within the launchSettings.json file. Within Visual Studio you'll find find the latter within your Project > Properties > launchSettings.json
There set the windowsAuthentication to false.
{
"iisSettings": {
"windowsAuthentication": false,
}
}
I currently have a MVC site, in .NET Core, backed by a public API. My users must log in (there are no [Anonymous] controllers), and authentication is already successfully being done using the DotNetCore.Authentication provider. All that is well and good.
What I'm now trying to do (by user request) is implement functionality for a user to read and view their Outlook 365 calendar on a page within my site. It doesn't seem too hard on the surface... all I have to do is have them authenticate through microsoftonline with my registered app, and then -- once they have given approval -- redirect back to my app to view their calendar events that I am now able to pull (probably using Graph).
In principle that seems really easy and straightforward. My confusion comes from not being able to implement authentication for a single controller, and not for the entire site. All of the OAuth2 (or OpenID, or OWIN, or whatever your flavor) examples I can find online -- of which there are countless dozens -- all want to use the authorization to control the User.Identity for the whole site. I don't want to change my sitewide authentication protocol; I don't want to add anything to Startup.cs; I don't want anything to scope outside of the one single controller.
tldr; Is there a way to just call https://login.microsoftonline.com/common/oauth2/v2.0/authorize (or facebook, or google, or whatever), and get back a code or token that I can use for that user on that area of the site, and not have it take over the authentication that is already in place for the rest of the site?
For anybody else who is looking for this answer, I've figured out (after much trial and error) how to authenticate for a single user just for a short time, without using middleware that authenticates for the entire application.
public async Task<IActionResult> OfficeRedirectMethod()
{
Uri loginRedirectUri = new Uri(Url.Action(nameof(OfficeAuthorize), "MyApp", null, Request.Scheme));
var azureADAuthority = #"https://login.microsoftonline.com/common";
// Generate the parameterized URL for Azure login.
var authContext = GetProviderContext();
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(_scopes, loginRedirectUri.ToString(), null, null, null, azureADAuthority);
// Redirect the browser to the login page, then come back to the Authorize method below.
return Redirect(authUri.ToString());
}
public async Task<IActionResult> OfficeAuthorize()
{
var code = Request.Query["code"].ToString();
try
{
// Trade the code for a token.
var authContext = GetProviderContext();
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(code, _scopes);
// do whatever with the authResult, here
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
return View();
}
public ConfidentialClientApplication GetContext()
{
var clientId = "OfficeClientId;
var clientSecret = "OfficeClientSecret";
var loginRedirectUri = new Uri(#"MyRedirectUri");
TokenCache tokenCache = new MSALSessionCache().GetMsalCacheInstance();
return new ConfidentialClientApplication(
clientId,
loginRedirectUri.ToString(),
new ClientCredential(clientSecret),
tokenCache,
null);
}
I don't know if that will ever be helpful to anybody but me; I just know that it's a problem that doesn't seem to be easily solved by a quick search.
I have an Intranet application with Windows authentication set for user authentication which works fine, only problem is that I do not want to say 'Hello, mydomain\user!' but use the user's full display name which I find in the Active Directory.
In fact I want to populate the profile with even more details from our domain, the problem is that I only want to do this AD query only once after the user has been authenticated on his first call to the application. I have all the AD and profile things working, but I do not find a good place to put the code so that it is called exactly once after login. I suspect a custom AuthorizeAttribute might be a way... Any help is greatly appreciated. Thanks!!
Try storing the information in session or within cookies or local storage on the client side.
Well, I finally came up with a solution - can this be considered as a as a valid answer? Basically I wrote a custom AuthorizationFilter and put a flag into the session to do the whole work only once. However I hoped to find an event "User_Authenticated" which is fired only once. But I guess this is more appropriate for Forms authentication.
public class ProfileUpdater : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// if there is a profile already in the session we do not update this
Controller controller = filterContext.Controller as Controller;
if (controller != null && controller.Session["ProfileUpdated"] != null)
{
return;
}
else if (controller == null)
{
return;
}
UserPrincipal domainUser = DomainHelper.GetDomainUser(controller.User.Identity.Name);
if (domainUser != null)
{
controller.Profile.SetPropertyValue("DisplayName", domainUser.DisplayName);
controller.Session["ProfileUpdated"] = true; // just put a marker object into the session to show we alreay updated the Profile
}
return;
}
}
Technologies I'm Using:
MVC v2
Forms Authentication (Sliding Expiration)
Session State Server
Custom Authorization Attribute
I'm using the state server process for my mvc app. During testing, when an authenticated user would click the "LogOff" button, it would correctly take them to the authentication screen, and upon successful credential entering, would log them back in. BUT, it would find their prior session variable state, and NOT reload any new permissions I'd given them. This is due to how I'm loading a user in the following code:
public override void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null)
throw new ArgumentNullException("FilterContext");
if (AuthorizeCore(filterContext.HttpContext)) {
IUser customUser = filterContext.HttpContext.Session["CustomUser"] as IUser;
if ((customUser == null) || (customUser.Name != filterContext.HttpContext.User.Identity.Name)) {
customUser = new User(filterContext.HttpContext.User.Identity.Name,
filterContext.HttpContext.User.Identity.IsAuthenticated);
}
if (_privileges.Length > 0) {
if (!customUser.HasAtLeastOnePrivilege(_privileges))
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
filterContext.HttpContext.Session["CustomUser"] = customUser;
}
}
So, you can see I'm storing my customUser in the Session and that value is what was fetched from the prior session even though the user had logged off between (but logged back on within the sliding expiration window.
So, my question is, should I place a simple Session.Abandon() call in my LogOff method in the AccountController, or is there a cleaner more advantageous way of handling this?
Normally Session.Clear() should be enough and remove all values that have been stored in the session. Session.Abandon() ends the current session. It might also fire Session_End and the next request will fire Session_Start.
I am storing user objects in session, pulling them out in the controllers, and sometimes write some data into them. but when to users post at the same time, the sessions get mixed fro some reason.
Does anyone have any idea how that is possible ?
typical post:
[HttpPost]
public ActionResult Index(QuestionModel model, FormCollection collection)
{
var person = ((Person)Session["Person"]);
if (!ModelState.IsValid)
{
ModelState.Clear();
ModelState.AddModelError("", Global.Global.error);
return View(new QuestionModel(person.page, (CultureInfo)Session["culture"]));
}
person.page = model.Page;
while (person.Answers.Count > model.Page - 1)
{
person.Answers.RemoveAt(person.Answers.Count - 1);
}
var answer = new Answer() { answer = model.ChosenAnswer, Question = "Q" + person.page };
person.Answers.Add(answer);
if (!CheckForNextPage(person.page)) { person.hasFinishedQuestions = true; return RedirectToRoute("Result"); }
person.page++;
return View(new QuestionModel(person.page, (CultureInfo)Session["culture"]));
}
I echo the session id on every page, and when a couple of users are using the website they get each others session + sessionid ...
#update: 3 experienced developers have been looking for the problem for 2 days, still no solution. already removed about 95% off the code, still same issue. server posts back responses from another session
This is not possible.
So this is my guess:
You are testing this wrongly, you are using different tabs from the same browser.
Some people don't know that this doesn't create a different session.
Try testing this on 2 different browsers (i.e. firefox and chrome) as they will not share the session (as the session id is normally stored in a cookie).
Please report back if this was the case.
We "solved" it. We didn't actually solve it, but we copied all the sources to a new project, recompiled, and everything worked. Untill this day, we still don't know why, and how that error happened ...