RoleManager Cookie Expiring Immediately - asp.net

This used to work, however I recently discovered that ASP.NET is not caching user roles in the cookie anymore. I ran a fiddler trace and it appears that the value for the cookie is blank and the expiration date is set in the past. Therefore the cookie is not sent on the subsequent request and the DB is hit every round trip.
I can't seem to find any posts on this. Any help would be great. Thanks!
web.config:
<roleManager enabled="true" defaultProvider="MyRoleProvider" cacheRolesInCookie="true" cookieName=".ASPXROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All" createPersistentCookie="false">
<providers>
<clear />
<add name="MyRoleProvider" type="MyCompany.Core.Web.Providers.MyRoleProvider" connectionStringName="MainConnect" applicationName="MyApplication" />
</providers>
</roleManager>
Fiddler Response (Header):
HTTP/1.1 200 OK
Cache-Control: private, s-maxage=0
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXROLES=; expires=Tue, 12-Oct-1999 05:00:00 GMT; path=/; HttpOnly
X-Powered-By: ASP.NET
Date: Mon, 31 Dec 2012 01:14:19 GMT
Content-Length: 1381

Take a look at This answer. It seems to indicate that only the IsUserInRole member of the provider will cache results this way. When checking user roles, ASP .NET MVC seems to use GetRolesForUser exclusively. I bumped into this same limitation not too long ago--here's some code I added to my role provider to provide a simple caching mechanism.
public class MyRoleProvider : RoleProvider
{
private readonly string userRoleCacheKeyFormat;
public MyRoleProvider()
{
userRoleCacheKeyFormat = this.Name + "_{0}";
}
public override string[] GetRolesForUser(string username)
{
return GetUserRoles(username);
}
private string[] GetUserRoles(string username)
{
string[] roleNames = null;
if (!TryGetCachedUserRoles(username, out roleNames))
{
//cache miss
roleNames = GetUserRolesFromStore(username);
}
return roleNames;
}
private bool TryGetCachedUserRoles(string username, out string[] userRoles)
{
string cacheKey = string.Format(userRoleCacheKeyFormat, username);
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
userRoles = (string[])httpContext.Cache.Get(cacheKey);
}
else { userRoles = null; }
return (userRoles != null);
}
private void CacheUserRoles(string username, string[] userRoles)
{
string cacheKey = string.Format(userRoleCacheKeyFormat, username);
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
httpContext.Cache.Insert(cacheKey, userRoles, null, DateTime.UtcNow.AddMinutes(15), Cache.NoSlidingExpiration);
}
}
private string[] GetUserRolesFromStore(string username)
{
MyDbContext db = MvcApplication.IoC.Resolve<MyDbContext>();
string[] roleNames = db.Users
.Single(u => u.Username == username)
.UserRoles
.Select(r => r.Name)
.ToArray();
CacheUserRoles(username, roleNames);
return roleNames;
}
}

I think. That Session doesn't have any Role.

try createPersistentCookie="true"

Related

Adding Connection: keep-alive header is not returned to client in ASP.net

Short Version
I'm adding the response header:
Connection: keep-alive
but it's not in the resposne.
Long Version
I am trying to add a header to an HttpResponse in ASP.net:
public void ProcessRequest(HttpContext context)
{
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Connection", "keep-alive");
context.Response.AppendHeader("AreTheseWorking", "yes");
context.Response.Flush();
}
And when the response comes back to the client (e.g. Chrome, Edge, Internet Explorer, Postman), the Connection header is missing:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Transfer-Encoding: chunked
Expires: -1
Server: Microsoft-IIS/10.0
AreTheseWorking: yes
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 26 Feb 2022 16:29:17 GMT
What am I doing wrong?
Bonus Chatter
In addition to trying AppendHeader:
context.Response.AppendHeader("Connection", "keep-alive"); //preferred
I also tried AddHeader (which exists "for compatibility with earlier versions of ASP"):
context.Response.AddHeader("Connection", "keep-alive"); // legacy
I also tried Headers.Add:
context.Response.Headers.Add("Connection", "keep-alive"); //requires IIS 7 and integrated pipeline
What am i doing wrong?
Bonus: hypothetical motivation for the question
By default keep-alive is not allowed in ASP.net.
In order to allow it, you need to add an option to your web.config:
web.config:
<configuration>
<system.webServer>
<httpProtocol allowKeepAlive="true" />
</system.webServer>
</configuration>
This is especially important for Server-Send Events:
public void ProcessRequest(HttpContext context)
{
if (context.Request.AcceptTypes.Any("text/event-stream".Contains))
{
//Startup the HTTP Server Send Event - broadcasting values every 1 second.
SendSSE(context);
return;
}
}
private void SendSSE(HttpContext context)
{
//Don't worry about it.
string sessionId = context.Session.SessionID; //https://stackoverflow.com/a/1966562/12597
//Setup the response the way SSE needs to be
context.Response.ContentType = "text/event-stream";
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Connection", "keep-alive");
context.Response.Flush();
while (context.Response.IsClientConnected)
{
System.Threading.Thread.Sleep(1000);
String data = DateTime.Now.ToString();
context.Response.Write("data: " + data + "\n\n");
context.Response.Flush();
}
}

ASP.NET and OWIN Cookies Azure Open ID is not working

I have try to use OpenID to connect with Azure AD, and I use the exact code from the tutorial https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp with no luck.
My Startup:
public class Startup
{
string clientId = System.Configuration.ConfigurationManager.AppSettings["ClientId"];
string redirectUri = System.Configuration.ConfigurationManager.AppSettings["RedirectUri"];
static string tenant = System.Configuration.ConfigurationManager.AppSettings["Tenant"];
string authority = String.Format(System.Globalization.CultureInfo.InvariantCulture,
System.Configuration.ConfigurationManager.AppSettings["Authority"], tenant);
/// <summary>
/// Configure OWIN to use OpenIdConnect
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailed
}
}
);
}
/// <summary>
/// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
My Login Page Code:
protected void Page_Load(object sender, EventArgs e)
{
try
{
if (!IsPostBack)
{
if (!Request.IsAuthenticated)
{
HttpContext.Current.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/AMS/Dashboard" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
else
{
var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
lblErrorMessage.InnerHtml = userClaims?.FindFirst("preferred_username")?.Value;
//check user info, and create session then redirect to Dashboard
}
}
}
catch (Exception ex)
{
//handle error
}
}
My Website structure is a little bit complicated as follow:
I have a website on the server x: mydomain.com
and I have a subdomain in server y: subdomain.mydomain.com
and I have my website AMS on server z with a redirect to subdomain.mydomain.com/AMS
now to solve cross-site cookie I use the following in web config
<outboundRules>
<rule name="Ensure httpOnly Cookies" preCondition="Missing httpOnly cookie">
<match serverVariable="RESPONSE_Set_Cookie" pattern="^(.*; path=/)" negate="false" />
<action type="Rewrite" value="{R:1}AMS; SameSite=none; secure; HttpOnly" />
</rule>
<preConditions>
<preCondition name="Missing httpOnly cookie">
<!-- Don't remove the first line! -->
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=none; secure; HttpOnly" negate="true" />
</preCondition>
</preConditions>
</outboundRules>
My Problem is Request.IsAuthenticated is always false, so the page keep redirect to the Microsoft login page
Any ideas?
Thanks in advance
Instead of the rewrite rule try this:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieSameSite = Microsoft.Owin.SameSiteMode.None,
CookieSecure = CookieSecureOption.Always
});
Also ensure the secure attribute is set as well
From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
SameSite NONE - Cookies will be sent in all contexts, i.e in responses to both first-party and cross-origin requests. If SameSite=None is set, the cookie Secure attribute must also be set (or the cookie will be blocked).

404 error at API while logging the app from simulator

I have created the simple login page in xamarin.forms,i have API for those logins,while running at postman iam getting the output,but while logging from the simulator iam getting the following error.
{StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=639506ba4afdd530b4429c0d57e89977accb4b666a1e17dbe3fcc5c1fce369d5;Path=/;HttpOnly;Domain=snovahub.azurewebsites.net
Date: Wed, 13 Sep 2017 13:23:00 GMT
Content-Length: 3485
Content-Type: text/html; charset=utf-8
}}
My Api method is as follows:
#region Get results from api
public static async Task<T> GetResultFromApi<T>(string serviceUrl,bool isTrue=true)
{
try
{
GetConnection();
var response = await _httpClient.GetAsync(new Uri(SnovaHubApiUrls.SnovaHubWebUrl + serviceUrl));
var stringAsync = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var responseJson = stringAsync;
return JsonConvert.DeserializeObject<T>(responseJson);
}
LoggingManager.Error("Received error response: " + stringAsync);
return default(T);
}
catch (Exception exception)
{
LoggingManager.Error(exception);
return default(T);
}
}
#endregion
The issue is that you are setting the HttpClient.BaseAddress and then also passing in a full URL when calling HttpClient.GetAsync(). You need to choose one or the other. So:
Option 1:
private static void GetConnection() {
if (_httpClient == null) {
_httpClient = new HttpClient { BaseAddress = new Uri(SnovaHubApiUrls.SnovaHubWebUrl) }; //You MUST place a / (slash) at the end of your BaseAddress ("http://something.com/api/" for example)
}
}
Then in your GetResultFromApi() method:
...
var response = await _httpClient.GetAsync(serviceUrl); //You MUST NOT place a slash at the beginning of 'serviceUrl' when using BaseAddress
Option 2:
private static void GetConnection() {
if (_httpClient == null) {
_httpClient = new HttpClient(); //Removed BaseAddress
}
}
Then in your GetResultFromApi() method:
...
var response = await _httpClient.GetAsync(new Uri(SnovaHubApiUrls.SnovaHubWebUrl + serviceUrl)); //Passing full URL

Syncfusion MVC grid unathorized with windows authentication

ASP.NET MVC 5 application with windows authentication.
WEB.CONFIG
...
<system.web>
<identity impersonate="true"/>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
</authorization>
...
CONTROLLER
...
public class TemplatesController : Controller
{
// GET: Templates
public ActionResult Index()
{
HRDataContext ctx = new HRDataContext();
var l = ctx.SurveyTemplates.ToList();
return View(l);
}
[AllowAnonymous]
public ActionResult Update(SurveyTemplate value)
{
//OrderRepository.Update(value);
//var data = OrderRepository.GetAllRecords();
return Json(value, JsonRequestBehavior.AllowGet);
}
[AllowAnonymous]
public ActionResult Insert(SurveyTemplate value)
{
//OrderRepository.Add(value);
//var data = OrderRepository.GetAllRecords();
return Json(value, JsonRequestBehavior.AllowGet);
}
[AllowAnonymous]
public ActionResult Delete(int key)
{
//OrderRepository.Delete(key);
//var data = OrderRepository.GetAllRecords();
var data = new List<SurveyTemplate>();
return Json(data, JsonRequestBehavior.AllowGet);
}
}
...
VIEW
#(Html.EJ().Grid<SurveyTemplate>("grdTemplate")
.Datasource(ds => ds.Json(Model).UpdateURL("Update").InsertURL("Insert").RemoveURL("Delete").Adaptor(AdaptorType.RemoteSaveAdaptor))
.EnableRowHover(false)
.AllowSelection()
.IsResponsive()
.AllowFiltering()
.AllowSorting()
.FilterSettings(filter => { filter.FilterType(FilterType.Menu); })
.EditSettings(edit => { edit.AllowAdding().AllowDeleting().AllowEditing(); })
.ToolbarSettings(toolbar =>
{
toolbar.ShowToolbar().ToolbarItems(items =>
{
items.AddTool(ToolBarItems.Add);
items.AddTool(ToolBarItems.Edit);
items.AddTool(ToolBarItems.Delete);
items.AddTool(ToolBarItems.Update);
items.AddTool(ToolBarItems.Cancel);
});
})
.Columns(col =>
{
col.Field("SurveyTemplateId")
.HeaderText("Id")
.IsPrimaryKey(true)
.TextAlign(TextAlign.Right)
.Width(75)
.Visible(false)
.Add();
col.Field("Name").Width(100).Add();
col.Field("Description").Width(150).Add();
})
)
Application can render Index view, but when I edit data grid calls controller but server response 'not authorized'.
CONTROLLER RESPONSE
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-StackifyID: V1|80000147-0003-ff00-b63f-84710c7967bb|
X-SourceFiles: =?UTF-8?B?QzpcS2Fyb2xcUHJvamVrdHlcSFIgLSBPY2VuYSBQcmFjb3duaWthXEhSIC0gT2NlbmEgcHJhY293bmlrYVxPY2VuYVByYWNvd25pa2FORVdcSW5zZXJ0?=
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
Date: Tue, 03 Jan 2017 22:55:49 GMT
Content-Length: 6128
Proxy-Support: Session-Based-Authentication
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IIS 10.0 Detailed Error - 401.2 - Unauthorized</title>
<style type="text/css">
<!--
How to enable authorization in grid calls or how to disable authorization in this controller?
Problem solved.
Problem was in grid definition:
Datasource(ds => ds.Json(Model).UpdateURL("Update").InsertURL("Insert").RemoveURL("Delete").Adaptor(AdaptorType.RemoteSaveAdaptor))
should be:
Datasource(ds => ds.Json(Model).UpdateURL("Templates/Update").InsertURL("Templates/Insert").RemoveURL("Templates/Delete").Adaptor(AdaptorType.RemoteSaveAdaptor))
Url should be full not only Action name.

WebException using reportviewer

I'm trying to connect to ReportingServices through the ASP.NET web ReportViewer control:
rvContract.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Remote;
rvContract.ServerReport.ReportServerCredentials = new ReportServerCredentials("myUsername", "myNetworkPassword", "DOMAIN");
rvContract.ServerReport.ReportServerUrl = new Uri(ReportConfiguration.ReportServerUrl);
string rptPath = ReportConfiguration.RootPath;
if (!rptPath.EndsWith("/"))
{
rptPath += "/";
}
rvContract.ServerReport.ReportPath = rptPath + "AdminReports/Contract";
List<ReportParameter> reportParams = new List<ReportParameter>();
if (MkoSession.AccountId.HasValue)
{
ReportParameter accountId = new ReportParameter("AccountId", MkoSession.AccountId.Value.ToString());
}
rvContract.ServerReport.SetParameters(reportParams);
rvContract.ShowParameterPrompts = false;
rvContract.ShowZoomControl = false;
rvContract.ServerReport.Refresh();
rvContract.DataBind();
The implementation of the credentials looks like this:
public class ReportServerCredentials : IReportServerCredentials
{
private string _userName;
private string _password;
private string _domain;
public ReportServerCredentials(string userName, string password, string domain)
{
_userName = userName;
_password = password;
_domain = domain;
}
public WindowsIdentity ImpersonationUser
{
get
{
// Use default identity.
return null;
}
}
public ICredentials NetworkCredentials
{
get
{
// Use default identity.
return new NetworkCredential(_userName, _password, _domain);
}
}
public bool GetFormsCredentials(out Cookie authCookie, out string user, out string password, out string authority)
{
// Do not use forms credentials to authenticate.
authCookie = null;
user = null;
password = null;
authority = null;
return false;
}
}
Just hard-coding my credentials while testing. Before check-in, we'll have to create a domain account for this.
I can hit both ReportService2005.asmx and ReportExecution2005.asmx (which is what my ReportServerUrl becomes) no problem through a browser.
When I get to the SetParameters call, I get a WebException. Looking at the headers in the Response within the Exception:
{RSNotAuthenticated: True
RSAuthenticationHeader: .ASPXFORMSAUTH
Content-Length: 206
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Tue, 24 Sep 2013 16:15:08 GMT
Location: /ReportServer/logon.aspx?ReturnUrl=%2freportserver%2fReportExecution2005.asmx
Server: Microsoft-HTTPAPI/2.0
X-AspNet-Version: 2.0.50727
}
which seems as if it's telling me that I haven't logged in. If that's the case, how do the exact same credentials allow me to see the web services through a browser?
BTW, I did set breakpoints in each of the methods in my ReportServerCredentials implementation and saw each breakpoint hit. Not sure what that's telling us, but the NetworkCredentials interface returned my network credentials just fine.
I think I might've stumbled into it. In case anyone else is having similar troubles.
First, I poked around and found the reporting services web.config on the server. Saw that it was using authenticaion mode = forms.
So, then I implemented the GetFormsCredentials to return true and pass a generic username. I got this username out of the Users table in the ReportServer database.
No longer getting the WebException, but my ReportViewer isn't displaying either. So, not out of the woods yet, but it seems closer.
FWIW, I think I remember somebody saying we had weird custom forms authentication on our db server. So, for anybody who happens upon this, YMMV. All of that predates me, so I'm not really sure how to check it out.

Resources