I am developing a ASPN.NET WEB API 1(with .NET framework 4.0) application with AngularJS, and I am using session to authenticate the users(I know it should be stateless, but for legacy purpose I am using session). In my application, in every request I make to my WEB API it creates a new session, even when I set values to my session.
The sessions is allowed in my appication through this code in Global.asax:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string origins = ConfigurationManager.AppSettings["cors-origins"];
bool hasSlash = origins.Substring(origins.Length - 1, 1) == "/";
if (hasSlash)
origins = origins.Substring(0, origins.Length - 1);
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", origins);
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
"Content-Type, Accept");
HttpContext.Current.Response.End();
}
}
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
Then I set values to my session in my controller:
public HttpResponseMessage Post(LoginViewModel model)
{
// SOME BUSINESS LOGIC HERE...
FormsAuthentication.SetAuthCookie(model.User, false);
HttpContext.Current.Session["usuario"] = model.User;
return Request.CreateResponse(HttpStatusCode.Accepted, "User successfylly logged in!");
}
But when I do another request to my application to access another method in controller, it throws me an error because session is null, like in this method:
public HttpResponseMessage Get()
{
var userName = HttpContext.Current.Session["usuario"];
return Request.CreateResponse(HttpStatusCode.Accepted, userName);
}
In my web.config, session is configured like this:
<sessionState mode="InProc" customProvider="DefaultSessionProvider" >
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
</providers>
</sessionState>
PS: It does not work on Chrome, but on IE it works, and doing request directly on postman it also works.
It was missing this line in my Application_BeginRequest
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials", "true");
And in every request on AngularJS I should pass withCredentials parameter as true. To achieve that, I put this line on my config file in AngularJS:
$httpProvider.defaults.withCredentials = true;
Related
I am developing a MVC 5 application with custom role provider, but it seems that the AuthorizeAttribute never call my customer role provider, my code is as below:
My Customer provider:
namespace MyDomain
{
public class CustomRoleProvider : RoleProvider
{
public override string[] GetRolesForUser(string username)
{
using (MyContext objContext = new MyContext())
{
var objUser = objContext.Users.FirstOrDefault(x => x.Username == username);
if (objUser == null)
{
return null;
}
else
{
string[] ret = { objUser.Access_Levels.Name };
return ret;
}
}
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = GetRolesForUser(username);
return userRoles.Contains(roleName);
}
}
My controller:
[Authorize(Roles = "Administrator")]
public class AdminController : Controller
And Web.Config:
<system.web>
<roleManager defaultProvider="CustomRoleProvider" enabled="true" >
<providers>
<clear />
<add name="CustomRoleProvider" type="Online_Storage_Portal.CustomRoleProvider" cacheTimeoutInMinutes="30"/>
</providers>
</roleManager>
</system.web>
Also my custom role provider is in the same project as my other controllers, I am able to call my custom role provider method with following code within my controller
String[] roles = Roles.GetRolesForUser(username)
but the controller with [Authorize(Roles = "Administrator")] always redirect the page to login screen even the user login and role are both valued.
Please help!!
I believe I've found the source of your problem. I'm going to assume you're using Windows Authentication, and trying to use your custom role provider in place of the Windows Groups that are automatically loaded. Looking into the MVC AuthorizeAttribute source, you'll find that it is actually calling Principal.IsInRole. Per MSDN:
InRole first checks the IsRoleListCached property to determine whether a cached list of role names for the current user is available. If the IsRoleListCached property is true, the cached list is checked for the specified role. If the IsInRole method finds the specified role in the cached list, it returns true.
If IsInRole does not find the specified role, it calls the GetRolesForUser method of the default Provider instance to determine whether the user name is associated with a role from the data source for the configured ApplicationName value.
So I'm guessing that because the Principal is a Windows Principal, it is coming in with it's roles populated and cached. When the IsInRole is called, it says 'Hey, I've already got roles, why would I go back to the provider to get them again?"
What you could do instead would be somthing like this:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
WindowsIdentity identity = HttpContext.Current.Request.LogonUserIdentity;
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new GenericIdentity(identity.Name), Roles.GetRolesForUser());
}
This will pull the windows identity off the HttpContext, use the name to explicitly fetch roles from your custom provider, and then slap a new GenericPrincipal on the request instead. I went further and implemented some logic to store the roles in an encrypted cookie so we don't have to go the role provider on each request.
void Application_PostAuthenticateRequest()
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket authTicket;
if (authCookie == null || authCookie.Value == "")
{
string[] getRoles = Roles.GetRolesForUser();
authTicket = new FormsAuthenticationTicket(1,
User.Identity.Name,
DateTime.Now,
DateTime.Now.AddMinutes(20),
true,
String.Join(";", getRoles));
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.Cookies.Add(authCookie);
}
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}
I'm having some dramas making a call against a ServiceStack REST service which I've secured with an out of the box IdentityServer STS.
I'm making an AJAX call against the REST endpoint, and I'm not sure how one might setup a logon procedure to get a security token to pass. The REST endpoint is on a different domain than the website making the call. The info that I've found so far all seems to revolve around the procedure where the client makes a call to the secured resource gets a 302 redirect to the identityserver logon page, then after successful authentication gets a 302 redirect to either the realm or the reply depending on the configuration. I've hooked all this up correctly and it works great if I'm simply browsing through the REST services. However with regards to my web app, AJAX and 302s aren't exactly best friends, so ideally what I think I'd like to have is a REST endpoint from the same ServiceStack website that takes a username and password and returns a security token without the complication of any redirects (I'll handle the 401 redirects within my web application itself which I'll get when I turn off passiveRedirectEnabled in the web.config). Any thoughts on how one might achieve this with IdentityServer?
Cheers,
Clint.
Completing the answer with the full REST endpoint:
In the ServiceStack web app:
Route to the logon endpoint in AppHost.cs with something like:
public override void Configure(Container container)
{
Routes.Add<Logon>("/logon", "POST");
}
Then there's a simple username/password Request DTO
public class Logon
{
public string UserName { get; set; }
public string Password { get; set; }
}
And the response DTO
Response DTO only needs to handle the POST - yes, you could add the URL/Password
as parameters in the URL for a GET request, but this does not sound like it's recommended.
In fact, you'd probably normally put this info in the Authorization header of the HTTP request
but this makes your job in ServiceStack a little harder.
public class LogonService : Service
{
public object Post(Logon request)
{
var securityToken = GetSaml2SecurityToken(request.UserName, request.Password, "https://myserver/identityserverwebapp/issue/wstrust/mixed/username", "http://myserver/servicestackwebapp/");
return SerializeRequestSecurityTokenResponse(securityToken);
}
private RequestSecurityTokenResponse GetSaml2SecurityToken(string username, string password, string endpointAddress, string realm)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(endpointAddress))
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var channel = (WSTrustChannel)factory.CreateChannel();
RequestSecurityTokenResponse requestSecurityTokenResponse;
channel.Issue(new RequestSecurityToken
{
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
AppliesTo = new EndpointReference(realm),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
}, out requestSecurityTokenResponse);
return requestSecurityTokenResponse;
}
private string SerializeRequestSecurityTokenResponse(RequestSecurityTokenResponse requestSecurityTokenResponse)
{
var serializer = new WSTrust13ResponseSerializer();
var context = new WSTrustSerializationContext(FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlerCollectionManager);
var stringBuilder = new StringBuilder(128);
using (var writer = XmlWriter.Create(new StringWriter(stringBuilder), new XmlWriterSettings { OmitXmlDeclaration = true}))
{
serializer.WriteXml(requestSecurityTokenResponse, writer, context);
writer.Flush();
return stringBuilder.ToString();
}
}
}
The ServiceStack web app Web.config should look fairly similar to this:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
</configSections>
<location path="FederationMetadata">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<!-- to allow the logon route without requiring authentication first. -->
<location path="logon">
<system.web>
<authorization>
<allow users="*" />
</authorization>
</system.web>
</location>
<system.web>
<httpHandlers>
<add path="*" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" />
</httpHandlers>
<compilation debug="true" />
<authentication mode="None" />
<authorization>
<deny users="?" />
</authorization>
<httpRuntime targetFramework="4.5" requestValidationMode="4.5" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<add path="*" name="ServiceStack.Factory" type="ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack" verb="*" preCondition="integratedMode" resourceType="Unspecified" allowPathInfo="true" />
</handlers>
</system.webServer>
<system.identityModel>
<identityConfiguration>
<audienceUris>
<add value="http://myserver/servicestackwebapp/" />
</audienceUris>
<issuerNameRegistry type="System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<trustedIssuers>
<add thumbprint="B6E05E14243FB7D76D5B660532520FB94679AA01" name="http://mycertificatefriendlyname" />
</trustedIssuers>
</issuerNameRegistry>
<certificateValidation certificateValidationMode="None" />
<securityTokenHandlers>
<securityTokenHandlerConfiguration saveBootstrapContext="true" />
</securityTokenHandlers>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
<cookieHandler requireSsl="false" />
<wsFederation passiveRedirectEnabled="false" issuer="https://myserver/identityserverwebapp/issue/wsfed" realm="http://myserver/servicestackwebapp/" requireHttps="false" />
</federationConfiguration>
</system.identityModel.services>
</configuration>
And finally, to authenticate a simple Javascript client app with the REST endpoint, POST the username and password to the logon endpoint of the servicestackwebapp, and then when you receive the response, post that back to the realm - doing so sets up the FedAuth cookies for your current session so you don't have to think about the token management client-side anymore.
$.ajax({
type: "POST",
url: "/servicestackwebapp/logon",
dataType: "text",
data: { UserName: "myuser", Password: "mypassword" },
success: function (data) {
$.ajax({
type: "POST",
url: "/servicestackwebapp/",
data: "wa=wsignin1.0&wresult=" + encodeURIComponent(data)
});
}
});
Also, I should note that all of the HTTP endpoints above should instead be going over HTTPS - don't be silly like I've done in my example and send clear-text claims over HTTP.
Also after I'd implemented my solution I found this: http://msdn.microsoft.com/en-us/library/hh446531.aspx
... I wish I'd found it before, but it's reassuring to know I've implemented something similar to the Microsoft example - we diverge at the point where they convert to a Simple Web Token - I keep it as a SAML token and pass that (serialized) to the client instead.
My solution so far:
I've exposed an endpoint on the REST service that makes a call against the WS-Trust endpoint that IdentityServer provides by default. In .NET 4.5, you'll need to have a reference to Thinktecture.IdentityModel as UserNameWSTrustBinding is not available in System.IdentityModel see: What's the .NET 4.5 equivalent to UserNameWSTrustBinding?
The code to get the SAML2 security token from the endpoint looks like this:
private SecurityToken GetSamlSecurityToken(string username, string password, string endpointAddress, string realm)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress(endpointAddress))
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var channel = factory.CreateChannel();
var securityToken = channel.Issue(new RequestSecurityToken
{
TokenType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0",
AppliesTo = new EndpointReference(realm),
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Bearer,
});
return securityToken;
}
This will authenticate based on a username and password and the endpointAddress parameter will look something like:
https://myserver/identityserverapp/issue/wstrust/mixed/username
Then I'm serializing the security token as follows:
private string SerializeSecurityToken(SecurityToken securityToken)
{
var serializer = new WSSecurityTokenSerializer();
var stringBuilder = new StringBuilder();
using (var writer = XmlWriter.Create(new StringWriter(stringBuilder)))
{
serializer.WriteToken(writer, securityToken);
return stringBuilder.ToString();
}
}
I believe the only remaining bit is establishing the FedAuth cookie(s), which I believe are set on first post of the security token to the secured web app.
Please weigh in with any improvements or suggestions. Thanks!
I am using a WCF service and currently it is consumed by a Silverlight application. The WCF service has clientaccesspolicy.xml and crossdomain.xml which is why there is no issue with making request from Silverlight even though the request comes from a different domain.
Now my understanding is that, adding following line of code on WCF service's web config built using .net 4.0 or above allows to make cross domain request using jsonp
<standardendpoint crossdomainscriptaccessenabled="true">
However, jsonp won't resolve my issue as I need to add custom request headers which jsonp doesn't allow as it's not really making XMLHttpRequest.
I am wondering, if I edit global.asax of WCF service with following snippet of code, will I be able to make AJAX cross domain request successfully?
protected void Application_BeginRequest(object sender, EventArgs e)
{
EnableCrossDomainAjaxCall();
}
private void EnableCrossDomainAjaxCall()
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content- Type, Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
Please note that I am having issue deploying the WCF project so I just wanted to know I am taking the right approach.
protected void Application_BeginRequest(object sender, EventArgs e)
{
EnableCrossDomainAjaxCall();
}
private void EnableCrossDomainAjaxCall()
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Foo");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
did the trick.
In my case, I also had to add one custom header named Foo as an example as I had to pass that from client side.
Hope it helps someone!
Like the question says, I wanted to know if it's possible to turn off caching on all controllers and actions for my entire site. Thanks!
Create a Global Action Filter and override OnResultExecuting():
public class DisableCache : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
filterContext.HttpContext.Response.Cache.SetNoStore();
}
}
And then register this in your global.asax, like so:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new DisableCache());
}
In summation, what this does is create a Global Action Filter so that implicitly this will be applied to all Controllers and all Actions.
You should add this method to your Global.asax.cs file
protected void Application_BeginRequest(object sender, EventArgs e)
{
Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate");
Response.AddHeader("Pragma", "no-cache"); // HTTP 1.0.
Response.AddHeader("Expires", "0"); // Proxies.
}
This disables cache on every request (images, html, js etc.).
Yes, depending on the approach you take.
I like applying the actions to a base controller (hence my reply there). You could implement the filter at the link below and implement it as a global filter as well (registered in your global.asax.cs)
Disable browser cache for entire ASP.NET website
In web.config you can add additional headers to go out with every response
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-control" value="no-cache"/>
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
I'm using logout method in web-app like below, but if i check remember me logout doesn't work, because cookie isn't cleared. How to clear programmatically this cookie in my method (or how to make better logout method) ?
public void logout() {
AnonymousAuthenticationToken anonymous = new AnonymousAuthenticationToken("anonymous", "anonymous", new ArrayList(Arrays.asList(new GrantedAuthorityImpl("ROLE_ANONYMOUS"))));
SecurityContextHolder.getContext().setAuthentication(anonymous);
}
If you are using the standard Spring Security cookie name (which is SPRING_SECURITY_REMEMBER_ME_COOKIE), you can do this:
void cancelCookie(HttpServletRequest request, HttpServletResponse response)
{
String cookieName = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);
}
You'll have to change the cookieName value if you are using a custom cookie name.
The AbstractRememberMeServices class has an implementation of LogoutHandler.logout which cancels the cookie. Inject the LogoutHandler and call this method.