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!
Related
For the past days I have been trying to figuring out how to make OAuth2 work on a native app with the OAuth2 client consisting of a separate frontend application with a Spring backend. Good news! I figured out a way to make it work both as web app (on a browser) as on a native (mobile) app. Here I would like to share my findings and ask for any suggestions on possible improvements.
Where Spring works out of the box
Spring Oauth2 works out of the box for web apps. We add the dependency <artifactId>spring-security-oauth2-autoconfigure</artifactId>. We add the annotation #EnableOAuth2Client. Furthermore, we add the configuration. For an in detail tutorial I would like to refer you to this tutorial.
Where challenges start to arise
Spring works with a session cookie (JSESSIONID) to establish a session which is send to the frontend using a Set-Cookie header. In a mobile application this Set-Cookie header is not send back to the backend on subsequent requests. This means that on a mobile application the backend sees each request as a new session. To solve this, I implement a session header rather than a cookie. This header can be read and therefore added to the subsequent requests.
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
However, that solves only part of the problem. The frontend makes a request using window.location.href which makes it impossible to add custom headers (REST call cannot be used because it would make it impossible to redirect the caller to the authorization server login page, because the browser blocks this). The browser automatically adds cookies to calls made using window.location.href. That's why it works on browser, but not on a mobile application. Therefore, we need to modify Spring's OAuth2 process to be able to receive REST calls rather than a call using window.location.href.
The OAuth2 Client process in Spring
Following the Oauth2 process the frontend makes two calls to the backend:
Using window.location.href a call to be redirected to the Authorization server (e.g. Facebook, Google or your own authorization server).
Making a REST GET request with the code and state query parameter to retrieve an access token.
However, if Spring does not recognise the session (like on mobile phone) it creates a new OAuth2ClientContext class and therefore throws an error on the second call: InvalidRequestException("Possible CSRF detected - state parameter was required but no state could be found"); by the AuthorizationCodeAccessTokenProvider.class. The reason it throws this error is because the preservedState property is null on the request. This is nicely explained by this post's answer of #Nico de wit.
I created a visual of the Spring OAuth2 process which shows the box 'Context present in session?'. This is where it goes wrong as soon as you have retrieved the authorization code from logging into the authorization server. This is because further on in in the getParametersForToken box it checks the preservedState which is then null because it came from a new OAuth2ClientContext object (rather than the same object that was used when redirecting the first call to the page of the authorization server).
The solution
I solved this problem by extending OAuth2ClientContextFilter.class. This class is responsible for redirecting the user to the authorization server login page if no authorization code has been retrieved yet. Instead of redirecting, the custom class now sends back a 200 and the in the body an url to which the frontend needs to be redirected. Also the frontend can now make a REST call rather than using window.location.href to be redirected. That looks something like:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
request.setAttribute(CURRENT_URI, this.calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
UserRedirectRequiredException redirect = (UserRedirectRequiredException)this.throwableAnalyzer.getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain);
if (redirect == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new NestedServletException("Unhandled exception", var10);
}
// The original code redirects the caller to the authorization page
// this.redirectUser(redirect, request, response);
// Instead we create the redirect Url from the Exception and add it to the body
String redirectUrl = createRedirectUrl(redirect);
response.setStatus(200);
response.getWriter().write(redirectUrlToJson(redirectUrl));
}
}
The createRedirectUrl contains some logic building the Url:
private String createRedirectUrl(UserRedirectRequiredException e) {
String redirectUri = e.getRedirectUri();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUri);
Map<String, String> requestParams = e.getRequestParams();
Iterator it = requestParams.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> param = (Map.Entry)it.next();
builder.queryParam(param.getKey(), param.getValue());
}
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
return builder.build().encode().toUriString();
}
I hope it helps others in the future by implementing OAuth2 using Spring on web and mobile applications. Feel free to give feedback!
Regards,
Bart
Environment
springBootVersion = '2.1.0.RELEASE'
Linux : 18.01 LTS
Context
I have the current method in controller:
#PostMapping(value = "")
public ResponseEntity<String> login(#RequestBody #Valid LoginCredentials loginCredentials) {
return ResponseEntity.status(HttpStatus.OK)
.header(HttpHeaders.SET_COOKIE, "Authorization=" + "Bearer " + token)
.build();
}
and my cors are configured right
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*")
.allowCredentials(true);
}
I also get on postman that the headers are got right
Still the header set-cookie is not sent.
The same happens when i use angular app to send the request with
return this.http.post(this.LOGIN_URL, loginCred, {withCredentials: true});
As you see, with credentials is set to true.
still I do not get my Set-cookie header.
Question
How can I have my header on the frontend (Angular or postman) ?
After hours of seraching, I found the main cause.
My frontent runs on localhost:4200
My backend runs on localhost:8090
when backend sends set cookie, google chrome blocks it, as the cookie is set by third party domain.
it can be inactivated in Settings > Advanced Settings > Privacy > Content Settings > Block third-party cookies and site data)
Work around
get the token in frontend as the body of the response, and set it there, by mean of frontend business logic (local storage, or set cookie)
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;
I'm having trouble requesting a simple GET on my local server with a token as a header.
My browser keep sending preflight request on simple GETÂ request.
I tried with postman / curl and i don't have any issue.
Here's the actual code :
Server (Spring) :
#Override
public void doFilter(final ServletRequest req, final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods",
"POST, GET, PUT, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, x-auth-token, Content-Type");
response.setHeader("Access-Control-Expose-Headers", "x-auth-token");
response.setHeader("Access-Control-Allow-Credentials", "true");
final HttpServletRequest request = (HttpServletRequest) req;
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
chain.doFilter(request, response);
}
}
So my x-auth-token here is a jwt-like token.
In my Angular code, I simply add my token as a x-auth-token like this :
getAll() {
return this.http.get('http://127.0.0.1:8080/module', this.jwt()).map((response: Response) => response.json());
}
private jwt() {
// create authorization header with jwt token
let token = localStorage.getItem('user');
let obj = JSON.parse(token)
console.log(obj.token.token)
if (obj.token) {
let headers = new Headers({
'x-auth-token': obj.token.token,
'Content-Type': "application/json"
});
return new RequestOptions({ headers: headers });
}
}
Note that my authentication / signup routes work fine, i'm only having trouble communicating my x-auth-token header to my Spring server.
Both my webserver are running locally : Spring on 8080 and Angular on 8000.
Any help would be greatly appreciated,
Thank you,
First, postman and curl are not good ways to test for CORS, since they don't enforce the same origin policies as standard browsers do. They will work even in cases that will fail in the browser.
Second, the reason your request is pre-flighted by the browser is that it is not a simple request, because of the x-auth-token header and the application/json Content-Type. Only application/x-www-form-urlencoded
multipart/form-data and text/plain content-types are allowed in simple requests.
I would assume that the problem is in the OPTION response. Make sure that it includes all the relevant headers. It might be possible it is failing or that you are loosing the added headers because you are handling it differently than other responses.
I need to store some information in session(or in whatever in ASP.NET Web API) that I need to retrieve in every API request. We will have one api IIS web site and multiple web site binding will be added through host header. When any request comes in for example, api.xyz.com, host header will be checked and store that website information in session that will be used in each subsequent api request when making a call to database.
I know there is no support for session in ASP.NET Web API. Is there any other way to handle this kind of situation? Where can I store information that can be retrieving in each subsequent request?
thanks.
in Global.asax add
public override void Init()
{
this.PostAuthenticateRequest += MvcApplication_PostAuthenticateRequest;
base.Init();
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
System.Web.HttpContext.Current.SetSessionStateBehavior(
SessionStateBehavior.Required);
}
give it a shot ;)
Well, REST by design is stateless. By adding session (or anything else of that kind) you are making it stateful and defeating any purpose of having a RESTful API.
The whole idea of RESTful service is that every resource is uniquely addressable using a universal syntax for use in hypermedia links and each HTTP request should carry enough information by itself for its recipient to process it to be in complete harmony with the stateless nature of HTTP".
So whatever you are trying to do with Web API here, should most likely be re-architectured if you wish to have a RESTful API.
With that said, if you are still willing to go down that route, there is a hacky way of adding session to Web API, and it's been posted by Imran here http://forums.asp.net/t/1780385.aspx/1
Code (though I wouldn't really recommend that):
public class MyHttpControllerHandler
: HttpControllerHandler, IRequiresSessionState
{
public MyHttpControllerHandler(RouteData routeData): base(routeData)
{ }
}
public class MyHttpControllerRouteHandler : HttpControllerRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyHttpControllerHandler(requestContext.RouteData);
}
}
public class ValuesController : ApiController
{
public string GET(string input)
{
var session = HttpContext.Current.Session;
if (session != null)
{
if (session["Time"] == null)
{
session["Time"] = DateTime.Now;
}
return "Session Time: " + session["Time"] + input;
}
return "Session is not availabe" + input;
}
}
and then add the HttpControllerHandler to your API route:
route.RouteHandler = new MyHttpControllerRouteHandler();
In WebApi 2 you can add this to global.asax
protected void Application_PostAuthorizeRequest()
{
System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
}
Then you could access the session through:
HttpContext.Current.Session
You can use cookies if the data is small enough and does not present a security concern. The same HttpContext.Current based approach should work.
Request and response HTTP headers can also be used to pass information between service calls.
Now in 2017 with ASP.Net Core you can do it as explained here.
The Microsoft.AspNetCore.Session package provides middleware for managing session state.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Adds a default in-memory implementation of IDistributedCache.
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSession();
}
From the Docs:
Introduction to session and application state in ASP.NET Core
Already tested on a working project