I'm having issues with updating claims on ASP.Net Identity 2.2.1 in .Net 4.6.2 / MVC5.
After updating a claim it will normally send an updated cookie to the browser and everything works fine but sometimes no set cookie header is sent to the browser.
I've not been able to identify any pattern as to when it happens other than when it is failing, the server is sending a
Persistent-Auth: true
http header value for every response in the session. I don't know what causes this header value to get set and it sometimes appears mid-session and once it starts sending it, it will be sent for the rest of the session and trying to update the claims will never work again for that session.
As far as I can see, I have hard-coded the isPersistent parameter to be false in every call into ASP.Net identity and I can't see anything else that could be related to this header.
The code I'm using for updating claims is
public static void UpdateClaims(List<Claim> claims)
{
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
var newIdentity = new ClaimsIdentity(HttpContext.Current.User.Identity);
foreach (Claim claim in claims)
{
Claim oldClaim = newIdentity.FindFirst(claim.Type);
if (oldClaim != null && oldClaim.Type != "")
{
newIdentity.RemoveClaim(oldClaim);
}
newIdentity.AddClaim(claim);
}
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant
(new ClaimsPrincipal(newIdentity), new AuthenticationProperties { IsPersistent = false });
}
This is being called from an MVC action method.
Does anyone have any suggestions what might be going wrong or even just a starting point of where to look? I don't know what causes that persistent-auth header but it looks to be related to the problem; whether it is the cause or a symptom of the problem, I don't know.
I'm using ASP.Net Identity 2.2.1 with .Net 4.6.2.
I'm running on Windows Server 2012R2 and the problem seems to occur with IE11, Chrome and Firefox.
I'm using Fiddler 4.6.3 to view the http headers / responses.
Update:
I have noticed that it seems to go wrong only when Windows authentication is enabled. My server has a setting that allows username/password, windows auth or both (user can choose to sign in as a different user using username/password). When windows auth is used, I initially authenticate the user using windows and then set a cookie, which I then use for all future requests in the session.
If windows auth is disabled, updating the claims like this always works. If windows auth is enabled, updating the claims usually works.
First, you're conflating two different things, although it's understandable since they're named similarly. The IsPeristent setting determines whether the cookie is a session-cookie or persistent cookie. In other words: it determines whether or not the cookie will expire when the browser is closed or at some predetermined time, whether or not the browser is closed.
The Persistent-Auth header is an optimization header that informs the client that it doesn't necessarily need to authorize each request. It has nothing to do with the IsPersistent flag.
Claims are set at login. Period. If you need to update the claims, you must sign the user out and sign them back in. This can be done programmatically (i.e. without user intervention), but it must be done. In other words, if you need to alter a claim, and you need that alteration to be available in the next request, then you follow it with:
Identity 2.0
AuthenticationManager.SignOut();
await SignInManager.SignInAsync(user);
Identity 3.0
await SignInManager.RefreshSignInAsync(user);
Instead of
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(new ClaimsPrincipal(newIdentity),
new AuthenticationProperties { IsPersistent = false });
you should use
authenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = false },
new ClaimsPrincipal(newIdentity));
I found the problem. It was using the wrong identity when it tried to update the claims. In my scenario there were two identity objects, one for windows authentication and one for cookie authentication. In most cases HttpContext.Current.User.Identity gets the cookie authentication object (which is the one with the claims) but occasionally it was giving me the windows authentication object, so when I tried to update the claims on that, it didn't do anything.
The problem was solved by replacing
var newIdentity = new ClaimsIdentity(HttpContext.Current.User.Identity);
with
ClaimsIdentity oldIdentity = claimsPrincipal.Identities.FirstOrDefault(i => i.AuthenticationType == "ApplicationCookie");
var newIdentity = new ClaimsIdentity(oldIdentity);
It now seems to work solidly without needing to sign out / back in again.
I guess the Persistent-Auth: true http header was being sent when OWin considered the Windows auth to be the primary identity so that is why its presence correlated with the inability to update claims.
I believe in our case it was the wrong/long-term IoC lifetime/'scope' of the ApplicationUserManager & possibly the same for the role manager too.
Related
Just looking into Blazor and my company decided to explore Server Side App (no WASM).
I got the basic project setup and because we want the app to work with out current user db, I modified our User Server (Microservice) to authenticate a user and return a JWT token. In the Login page once we have the token, we authenticate and the cookie is stored.
var auth = await HttpContext.AuthenticateAsync();
auth.Properties.RedirectUri = "/";
var accessToken = new AuthenticationToken()
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = Token
};
AuthenticationToken[] tokens = { accessToken };
auth.Properties.StoreTokens(tokens);
auth.Properties.IsPersistent = true;
await HttpContext.SignInAsync("Cookies", auth.Principal, auth.Properties);
So I want to say at this point everything is working fine.
Now back to the question. I wish to increase security by using Refresh Tokens, and this is where I hit the problem. Every tutorial or guide I google/bing that talks about Refresh tokens goes by the basses that I am using WASM or an API that I call. There are a few articles that say to use Local Storage but then that can bring up a whole new conversation of Local Storage vs Cookies vs Session, and I was hoping to stick with most of the built in stuff.
I understand that Server-Side issues with the HTTP context, hence why the login page is done on a .cshtml page and the fact that the cookies are set as HTTPOnly so JavaScript cannot access them.
So, is there away to do it?
Just to clarify, this is a Server-Side app only, no client apps or API's or anything else and I wish to stick with most of the built in stuff using cookies.
We have a .NET Core 6 Blazor Server App. We login with our own Identity Provider using OIDC. We are having an issue signing out.
We have set up our authentication using the following code block.
builder.Services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddCookie()
.AddOpenIdConnect(opts => {
opts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.RequireHttpsMetadata = !isDebug;
opts.ClientId = "user-accounts-app";
opts.CallbackPath = "/signin-oidc";
opts.ResponseType = OpenIdConnectResponseType.Code;
opts.Authority = authority;
opts.ClientSecret = builder.Configuration["CLIENT_SECRET"];
var scopes = new List<string>() {
"openid", "profile", "email", "phone", "offline_access"
};
foreach(var s in scopes)
{
opts.Scope.Add(s);
}
});
The discovery document does include an end_session_endpoint; however, the endpoint is never hit. We attempt to signout from a razor page with
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// This line does not work
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
{
RedirectUri = "http://mydomainhere.com/our/path/here",
});
Running that 2nd SignOutAsync seems to do nothing. The Identity Provider is not hit at the end session endpoint and nothing happens on our logout page. Our session is not cleared from the IDP.
Additionally, our cookies for the blazor app are not entirely cleared. We have a ton of lingering .AspNetCorrelation.hash<hash-here> with path /signin-oidc (tried to get a screenshot but SO is having server errors with those right now). But the .AspNetCore cookie is cleared successfully by the first SignOutAsync call.
I'm not sure what the behavior of the second SignOutAsync is supposed to be. Would it redirect the user to the logout url of the IDP? Or does it do that in the background? Are we missing some configuration in our call to AddOpenIdConnect() to handle sign out?
Looks like we were just missing an OIDC sign out scheme.
opts.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;
This is all we needed to get it working.
ASP.net will use the sign in scheme if no sign out scheme is specified. The sign in scheme is cookie which is a bit misleading because the OpenID authority is actually the one you're signed into. Signing in leads to the cookie being created to store the auth token provided by that authority (so you are effectively signed into the client app). If you sign out with a cookie scheme then only the cookie is destroyed -- you are signed out of the client, but not the authority. The next time you come to a page, you just get a new cookie because you're already signed into the authority.
The sign out scheme above therefore signs out of the authority, not just the client. I'm not sure if my colleague who figured it out also added a step of removing the cookie. I will edit this with details if I find out they did or not. It may somehow be magically handled by the asp framework.
I have an IDP server implemented by Duende IdentityServer assume which is hosted on idp.com and there are two separate ReactJS applications hosted on app.mysite.com and profile.mysite.com and they are using JWT token for authentication and authorization process. now when I login into app.mysite.com through idp.com profile.mysite.com is un unauthenticated and needs another login. I use the same client configuration for both of these sites. I know there are some methods such as using an IFRAME inside client code to share the JWT token between these two app but I am looking for a built-in approach inside the Identity server to solve this issue?
First of all, if you have 2 CLIENTS, you should configure 2 separate configurations for both of them.
Afer separation of clients you should rely on cookie set on idp.com after first authentication. (Good to know - How to setup cookie authentication basic cookie authentication: https://learn.microsoft.com/pl-pl/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0)
Anyway, if you configured IdentityServer properly, it handles cookie authentication "out-of-the-box" - so probably the only thing you have to do is to Signin the user.
AuthenticationProperties props = new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.Add(LoginOptions.RememberMeLoginDuration)
};
var issuer = new IdentityServerUser(user.SubjectId)
{
DisplayName = user.Username
};
await HttpContext.SignInAsync(issuer, props);
When the youser want to login to second application, after start of the flow (eg. code flow) and redirect to the idp.com, idp.com knows that the user is already signed-in (cookie) and should immediately generate token and redirect back to the return url.
If you need you can adjust custom behaviours using IProfileService.
I am trying to come up with a solution for the following scenario:
I have migrated a web app from forms auth, to OWIN+OAuth.
Now, there is also a mobile client app ( android ). The mobile client is authenticated against the web app, if everything goes well the server will respond with the user's data and it used to respond with 2 cookies as well, one been the session cookie ( never used, but its presence was checked on the client ) and the other one was the user's identity cookie. The mobile client validates that the user's data "look good" and it also checks for the presence of those 2 cookies in order to pass the login screen.
What i am trying to do right now is to keep the mobile app unaffected by the changes on the server, by supplying the user's data as needed after all the required checks, and then mocking the presence of the 2 cookies, so that i can avoid making any changes on the mobile apps ( avoid the need to compile and update the clients that is )
The code below is implemented in an API controller, and while the .ASPXAUTH cookie seems to work fine , the old ASP.NET_SessionId cookie seems uncontrollable.
The code below will never respond with the ASP.NET_SessionId cookie to the client, although the code is the same as it is for the other cookie, which will completely obey either .Add/.Set/.Remove on the collection.
if (HttpContext.Current.Response.Cookies.AllKeys.Contains("ASP.NET_SessionId"))
HttpContext.Current.Response.Cookies.Set(new HttpCookie("ASP.NET_SessionId", "1st") { HttpOnly = false });
else
HttpContext.Current.Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", "1st") { HttpOnly = false });
if (HttpContext.Current.Response.Cookies.AllKeys.Contains(".ASPXAUTH"))
HttpContext.Current.Response.Cookies.Set(new HttpCookie(".ASPXAUTH", "2nd") { HttpOnly = false });
else
HttpContext.Current.Response.Cookies.Add(new HttpCookie(".ASPXAUTH", "2nd") { HttpOnly = false });
Is there any reason that i am unable to return a "custom cookie" named ASP.NET_SessionId ?
I have removed all logic related to forms auth, so that cookie is not generated by default anymore, but it looks like i can not fake return it either.
Thanks in advance
This is more of a design/approach question...
I think I'm missing something here. We're building an Asp.Net MVC 5 web application and securing it with Azure AD using the following scenario:
https://azure.microsoft.com/en-us/documentation/articles/active-directory-authentication-scenarios/#web-browser-to-web-application
https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect
The token/cookie is an absolute expiry and expires after one hour. So what does that do for the user experience? Every hour they have to log back in no matter what? In our testing, when the user expires, the browser is redirected back to AD and the user prompted for credentials. This, of course, breaks any AJAX calls we have loading partial views and none of our DevExpress controls are stable as a result.
Based on the response to this SO post: MVC AD Azure Refresh Token via ADAL JavaScript Ajax and KnockoutJs
...what I'm seeing is expected? It seems to me like not a very viable solution for a cloud hosted line-of-business application where users are logged in and working all day.
Am I missing something? Or is this just not an ideal scenario for business apps?
We faced a similar set of problems, as well as the same thoughts about how you could use Azure AD with ASP.NET MVC in web apps with such a low session timeout (60 minutes).
The solution we came up with, that seems to be working (albeit with limited testing), is to have an iFrame on the page that we refresh every 5 minutes.
<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms" id="refreshAuthenticationIframe" src="#Url.Action("CheckSessionTimeout", "Home", new { area = "" })" style="display:none;"></iframe>
The "CheckSessionTimeout" page is basically blank.
In a Javascript file referenced by the whole app, we have:
var pageLoadTime = moment();
setInterval(refreshAuthenticationCookies, 1000);
function refreshAuthenticationCookies() {
if (moment().diff(pageLoadTime, "seconds") > 300) {
document.getElementById("refreshAuthenticationIframe").contentDocument.location = "/Home/ForceSessionRefresh";
pageLoadTime = moment();
}
}
(NB: moment is a JS date/time library we use). On the Home controller, we have:
public ActionResult CheckSessionTimeout() => View();
public ActionResult ForceSessionRefresh()
{
HttpContext.GetOwinContext()
.Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Home/CheckSessiontimeout" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
return null;
}
I am not sure if any of that is the best way/approach. It's just the best we can do to fix up what seems like a set of difficult constraints with Azure AD and ASP.NET MVC apps (that are not SPAs, not using Web API but are using Ajax calls), relative to where we are coming from where none of this matters with on-premises apps doing Kerberos auth (and our user's expectations that session timeout is nothing they want to see or worry about).
There are two ways to handle this (at least this is how we are doing it in our application; it would be interesting to see what AD gurus have to say about this so that we can also fix it if it is not the right way to to do things):
General Approach - Use Refresh Token
When you get an access token from AD, today you get 3 things back - access token, access token expiry and a refresh token. What you do is cache all three of them in your application. Till the time access token is expired, you can simply use that access token. Once the token is expired, you can make use of refresh token to get a new access token. The method in ADAL you want to use for this purpose is AcquireTokenByRefreshToken.
Having said that, you should not take a hard dependency in your application on Refresh Token. Based on the best practices described here, a refresh token can expire or invalidated. Furthermore based on Vittorio's post, a refresh token is not even returned in ADAL version 3. So you may want to consider that.
Other Approach - Acquire Token Silently
Other approach you could take is acquire a new token silently on behalf of the user once the token expires. I believe this requires that a user must sign in manually at least once in your application and follow the OAuth2 flow. The method you want to use is AcquireTokenSilent.
Here's the pseudo code for our approach:
var now = DateTime.UtcNow.Ticks;
if (now <= tokenExpiry && !string.IsNullOrWhiteSpace(accessToken))
return accessToken;
var clientCredential = new ClientCredential(ClientId, ClientSecret);
var authContext = new AuthenticationContext(string.Format("{0}/{1}",
AzureActiveDirectorySignInEndpoint,
azureADTenantId));
AuthenticationResult authResult = null;
if (!string.IsNullOrWhiteSpace(refreshToken))
{
authResult = await authContext.AcquireTokenByRefreshTokenAsync(refreshToken,
clientCredential,
ADEndpoint);
}
else
{
authResult = await authContext.AcquireTokenSilentAsync(Endpoint,
clientCredential,
new UserIdentifier(userId, UserIdentifierType.UniqueId));
}
return authResult.AccessToken;//Also you may want to cache the token again