Telerik Reporting REST API not accepting cross origin requests - asp.net-web-api

My API has a ReportsController set up like so:
using System.Web.Http.Cors;
using Telerik.Reporting.Cache.File;
using Telerik.Reporting.Services;
using Telerik.Reporting.Services.WebApi;
namespace API.Controllers
{
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ReportsController : ReportsControllerBase
{
static ReportServiceConfiguration configurationInstance;
static ReportsController()
{
configurationInstance = new ReportServiceConfiguration
{
HostAppId = "Html5App",
Storage = new FileStorage(),
ReportResolver = new ReportTypeResolver(),
// ReportSharingTimeout = 0,
// ClientSessionTimeout = 15,
};
}
public ReportsController()
{
//Initialize the service configuration
this.ReportServiceConfiguration = configurationInstance;
}
}
}
My App_Start\WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
My Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ReportsControllerConfiguration.RegisterRoutes(GlobalConfiguration.Configuration);
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
My web.config has the recommended binding redirects:
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.6.0" newVersion="5.2.6.0" />
</dependentAssembly>
As far as I can tell, everything is set up correctly, I can call api/reports/formats and see the correct data. When I try to load this report, I get an error.
$("#reportViewer1").telerik_ReportViewer({
serviceUrl: "http://dev-api/api/reports",
reportSource: {
report: "Logic.Reports.Report1, Logic",
parameters: reportParam
},
});
It displays:
"Error loading the report viewer's templates. (Template = http://dev-api/api/reports/resources/templates/telerikReportViewerTemplate-html)."
on the page, and displays
Failed to load http://dev-api/api/reports/resources/templates/telerikReportViewerTemplate-html: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:64634' is therefore not allowed access.
in the Chrome console. I cannot figure out what I am missing.

You need to Allow CORS in your WEB API See this doc :Enable CORS
Manage NuGet Package by installing Microsoft ASP.NET CORs
Add Following lines of code in web.Config under System.WebSe
Add Following Lines of code in your web.config in system.WebServer section
.
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
Add [EnableCors(origins: "", headers: "", methods: "*")] in your Controller

Related

A new session is being created in every request on web api

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;

MVC Custom Membership Provider and AccountController.cs

I have implemented my own custom MembershipProvider, configured it within web.config and my initialize method is successfully being called, even though it only contains:
public override void Initialize(string name, NameValueCollection config)
{
base.Initialize(name, config);
}
I have also implemented all the other standard methods like:
public override bool ChangePassword(string username, string oldPassword, string newPassword)
and
public override bool ValidateUser(string username, string password)
My understanding of the benefits of bothering to write a custom MembershipProvider was that once you've implemented the interface and initialized it, the appropriate methods would automatically get called at the appropriate times by the standard MVC 5 web site. I.e. when a user logs in or registers etc. But none of my methods are being called, apart from Initialize()...
Am I expected to have to go through the standard AccountController.cs and change all of the methods in there to force it to call my custom MembershipProvider?
E.g. this is the standard Login() method:
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "Invalid username or password.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Do I have to modify this to make sure its calling my stuff?
Or what am I missing?
Thanks.
On your web.config at the bottom of your solution, paste this under <system.web>
//This is just easy to modify
<membership defaultProvider="YourCustomMembershipProvider">
<providers>
<clear /> //this clears up all the default membership called on init
<add name="YourCustomMembershipProvider" passwordFormat="Hashed" type="ProjectName.YourFolder.YourCustomMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" />
</providers>
</membership>
<roleManager enabled="true" defaultProvider="YourCustomRoleProvider">
<providers>
<clear />
<add name="YourCustomRoleProvider" type="ProjectName.YourFolder.YourCustomRoleProvider" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" writeExceptionsToEventLog="false" />
</providers>
</roleManager>
Hope this helps. :)

AJAX call against REST endpoint secured with Thinktecture's IdentityServer STS

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!

Custom Errors works for HttpCode 403 but not 500?

I am implementing custom errors in my MVC3 app, its switched on in the web.config:
<customErrors mode="On">
<error statusCode="403" redirect="/Errors/Http403" />
<error statusCode="500" redirect="/Errors/Http500" />
</customErrors>
My controller is very simple, with corresponding correctly named views:
public class ErrorsController : Controller
{
public ActionResult Http403()
{
return View("Http403");
}
public ActionResult Http500()
{
return View("Http500");
}
}
To test, I am throwing exceptions in another controller:
public class ThrowingController : Controller
{
public ActionResult NotAuthorised()
{
throw new HttpException(403, "");
}
public ActionResult ServerError()
{
throw new HttpException(500, "");
}
}
The 403 works - I get redirected to my custom "/Errors/Http403".
The 500 does not work - I instead get redirected to the default error page in the shared folder.
Any ideas?
I've got 500 errors up and running by using the httpErrors in addition to the standard customErros config:
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="403" subStatusCode="-1" />
<error statusCode="403" path="/Errors/Http403" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/Errors/Http500" responseMode="ExecuteURL" />
</httpErrors>
</system.webServer>
And removing this line from global.asax
GlobalFilters.Filters.Add(new HandleErrorAttribute());
Its not perfect however as I'm trying to retrieve the last error which is always null.
Server.GetLastError()
See https://stackoverflow.com/a/7499406/1048369 for the most comprehensive piece on custom errors in MVC3 I have found which was of great help.
I've got the same problem, I catch the Exception directly in Global.asax in that case:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
var code = httpException == null ? 500 : httpException.GetHttpCode();
// Log the exception.
if (code == 500)
logError.Error(exception);
Server.ClearError();
Context.Items["error"] = code;
RouteData routeData = new RouteData();
routeData.Values.Add("controller", "Error");
routeData.Values.Add("action", "Index");
routeData.Values.Add("code", code);
IController errorController = new ErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
}
That redirects to my custom Error 500: /Error/Index?code=500

How do I turn off caching for my entire ASP.NET MVC 3 website?

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>

Resources