Angular 2 - Consuming restful api calls with windows authentication - node.js

I have a .net web api hosted on IIS 7 on a remote server which uses windows authentication. I want to access the web api using Angular 2 using TypeScript with node. Earlier i was getting an error 'Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource'
I added this on the hosted Application's web.config
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
But now i get Unauthorised 401 error. I have read about adding the following code to allow cross domain access - but I don't have any idea where do i add this in the angular 2 app and how to compile.
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.get('/', function(req, res, next) {
// Handle the get for this route
});
app.post('/', function(req, res, next) {
// Handle the post for this route
});
Here is the sample code for service that I am trying to make the get call with
#Injectable()
export class TodoService {
todos$: Observable<Todo[]>;
private _baseUrl: string;
private _todosObserver: Observer<Todo[]>;
private _dataStore: {
todos: Todo[]
};
constructor(private _http: Http) {
//let headers: Headers = new Headers();
this._baseUrl = 'http:/SampleServer/Server/api/LoadTodo';
this.todos$ = new Observable(observer => this._todosObserver = observer).share();
this._dataStore = { todos: [] };
}
loadTodos() {
let headers: Headers = new Headers();
//headers.append('Access-Control-Allow-Origin');
headers.append('Authorization', 'Basic ' +
btoa('username:password'));
//let opts: RequestOptions = new RequestOptions();
//opts.headers = headers;
this._http.get(`${this._baseUrl}`,headers).map(response => response.json()).subscribe(data => {
this._dataStore.todos = data;
this._todosObserver.next(this._dataStore.todos);
}, error => console.log('Could not load todos.'));
}
Any help to resolve this issue would be great.

You need to check if the Authorization header is correctly sent within your request. If you forgot to import the Headers class, the header won't be sent:
import {Http, Headers, ...} from 'angular2/http';
Another option would be that, since you are in the case of a preflighted request (GET method with the Authorization header), an OPTIONS request is sent. In fact, this request is transparently sent by the browser and the credentials are present in it. So you don't have to check security here on the server side. Otherwise you will have a 401 error since the server won't be able to authenticate the request...
See these articles for more details:
http://restlet.com/blog/2015/12/15/understanding-and-using-cors/
http://restlet.com/blog/2016/09/27/how-to-fix-cors-problems/

I have stumbled upon the same problem when using Angular 2 within an ASP.NET Core solution. For me, I had the explicitly specify withCredentials: true:
getSomeModels() {
return this.http.get(this.apiBasePath + 'models', { withCredentials: true })
.map(res => res.json());
}
CORS must be enabled on the server side (Web API) and no other changes were required on the client side.
NOTE: Server side code to enable CORS (Startup.cs)
public void ConfigureServices(IServiceCollection services)
{
// injected services
services.AddAuthorization();
services.AddMvc();
//db context
var corsBuilder = new CorsPolicyBuilder();
corsBuilder.AllowAnyHeader();
corsBuilder.AllowAnyMethod();
corsBuilder.AllowAnyOrigin(); // For anyone access.
corsBuilder.AllowCredentials();
services.AddCors(options =>
{
options.AddPolicy("SiteCorsPolicy", corsBuilder.Build());
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// middleware configuration
app.UseCors("SiteCorsPolicy");
}

When you use Windows authentication in a REST API and make calls from a client with Angular (or any other JavaScript solution), you have to do some configuration on both sides: server and client.
Server
If you only enable Windows authentication for the REST API from IIS, you are likely to get the 401 error from the client, since browsers make an OPTIONS call before making the original call to validate CORS access.
To enable this validation, you must also enable Anonymous Authentication in your REST API from IIS.
And add this exception in the web.config file of the REST API:
<system.webServer>
.....
<security>
<authorization>
<add accessType="Allow" verbs="OPTIONS" users="?" />
<add accessType="Deny" verbs="GET, PUT, POST, DELETE" users="?" />
</authorization>
</security>
</system.webServer>
Where Anonymous Authentication is allowed for OPTIONS type requests, in order to allow CORS validation from the browser. And Anonymous Authentication is denied for the rest of the operations: GET, PUT, POST, DELETE.
You must consider that the OPTIONS method will be enabled for all anonymous calls and you should not use it in your REST API if you are going to validate the authentication.
Client
On the other hand, in the client you can enable only the Windows authentication in the IIS where it's hosted. And from the JavaScript API call, include the parameter { withCredentials: true }, to specify that the call must include the credentials:
callApiPost(url: string, body: object) {
return this.http
.post(`${environment.api}/${url}`, body, { withCredentials: true })
.pipe(catchError(this.handleError));
}

Related

Cant set cookie when CORS request comes from iPhone. Works for windows and mac users

The Setup / Environment
Client: (React.js, vs code, axios)
POST request to backend server to set auth cookie.
On every refresh Ill verify the cookie by a GET request to the auth backend server.
Every axios call is done with the "withCredentials:true" property set.
Backend (.net 6 - miminal API app written in c# and visual studio 2022.)
Set the cookie to "httpOnly", "sameSite=none" and "secure".
What Works
I have tested the flow on Windows 10, 11 + Mac computer and here everythink works fine. The cookie is set and I can login to my app.
The setCookie header is present here.
The problem
When I try to login from my iPhone with the latest IOS 15.4 it is not working (though it should be supported according to this https://caniuse.com/same-site-cookie-attribute).
The cookie is not set and the "getcookie" request returns null and Ill return Unauthorized.
Code:
Frontend (React js):
//run "npx create-react-app iphone-cors-test-app" and add a useEffect hook in App component //like this.
useEffect(() => {
var urlToBackendService= "https://829f-217-211-155-130.eu.ngrok.io";
axios({
url: baseURL + '/setcookie',
method: 'post',
withCredentials: true,
data: {
Email: 'ineedhelp#please.com',
Password: 'loveu'
}
}).then(() => {
axios({
url: baseURL + '/getcookie',
method: 'get',
withCredentials: true
}).then((resp) => {
var cookieValue = resp.data;
console.clear();
console.log(`cookie value: ${cookieValue}`);
alert(`cookie value: ${cookieValue}`);
})
});
Backend (c# .net 6, visual studio 2022):
//.net core web api template > minimal api (no controllers) enable https.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("https://nameofmyreactapp.azurewebsites.net")
.WithHeaders("accept", "content-type", "origin")
.WithMethods("GET", "POST", "OPTIONS")
.AllowCredentials();
});
});
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors();
app.MapPost("/setcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor, LogonRequest logonRequest) =>
{
return await Task.Run<IResult>(() =>
{
//login user and get an accesstoken. set accesstoken to a httpOnly cookie.
var accessToken = "newly generated jwt access token";
httpContextAccessor.HttpContext!.Response.Cookies.Append(key: "accesstoken", accessToken, new CookieOptions
{
HttpOnly = true,
/*This should work with an iPhone with ios 15.4 (https://caniuse.com/same-site-cookie-attribute).*/
SameSite = SameSiteMode.None,
Secure = true
});
return Results.Ok();
});
});
app.MapGet("/getcookie", async ([FromServices] IHttpContextAccessor httpContextAccessor) =>
{
return await Task.Run<IResult>(() =>
{
var accesstoken = httpContextAccessor.HttpContext!.Request.Cookies["accesstoken"];
return string.IsNullOrEmpty(accesstoken)
? Results.Unauthorized()
: Results.Ok(accesstoken);
}
);
});
app.Run();
public record LogonRequest(string Username, string Password);
Screenshots:
setCookie request.
getCookie request.
Please help me.
UPDATE
If you want to test this with your phone I use ngrok. Sign up and follow directions. Download ngrok.exe and go to that folder in your terminal. Then start your backend localhost and type "ngrok http + your localhost address".
Example:
"ngrok http https://localhost:7200"
Then hit enter and you will get a public url to your localhost.
Replace the client axios url (urlToBackendService) with this new public url and publish your react app to to cloud (or create another ngrok user and do the same think for the client) or if you have browserstack account or if you have another idé how to test this.
I just want to clarify the solution here.
(Solution 2 is the best practice version here if you just want the short version)
Solution 1
I knew that it probably should work if my two sites where hosted on the same domain but since I was in early stages in development and I didnt want to create custom domains just yet, and I also had read documentation that interpreted that is should work anyways I continued with this solution.
So my first solution (which is not idéal since localstorage is not as safe as a secure httponly cookie) was to change my auth server to be able to receive accesstoken via headers and cookies, I also made sure to return tokens in the response so I could store the tokens in localstorage. Flow example:
login with username & password and send form to auth server.
Get tokens from auth server response and store in a local storage variable.
Send a request to auth server with accesstoken header provided by localstorage variable.
Solution 2 (Best practice version)
Cred to my fellow user #AndrewLeonardi and the original post from #RossyBergg which could confirmed what I expected, that it will work if I just put the two services on the same domain. I ended up with this solution:
AuthService url: https://auth.domain.se
Client url: https://domain.se
The httpOnly secure cookies was now working properly and I was able to get, set & remove the cookie in the auth server. The header & localstorage implementation from "Solution 1" could be removed.

How can I get CORS to work for API calls with a Hapi v20 server with HTTPS?

Basic problem: you've followed the tutorial, you've fired up the Hapi server, it's running... but it doesn't work. A direct call via curl will get something, using a web browser to directly load the API call will get something... but using that API endpoint within an app, say, Angular or React, and it bombs out with an error message like:
Access to XMLHttpRequest at 'https://localhost:3000/server/ping' from origin 'http://localhost:5000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And it's true: you check the headers, and Access-Control-Allow-Origin is not on that list at all. So your app, having gotten blocked here in the preflight request, isn't even going to make the actual GET/POST call.
Here's the full file of a fully working Hapi v20.2.0 server, in TypeScript:
'use strict'
import * as fs from 'fs'
import * as util from 'util'
import * as path from 'path'
import * as os from 'os'
import * as Hapi from '#hapi/hapi'
import * as Http2 from 'http2'
const strFullNameCert:string=path.resolve(
os.homedir(),
'ssl',
'domain.crt')
const strFullNameKey:string=path.resolve(
os.homedir(),
'ssl',
'domain.key')
const key :Buffer = fs.readFileSync(strFullNameKey)
const cert:Buffer = fs.readFileSync(strFullNameCert)
const sslDetails ={key,cert}
const server = new Hapi.server({
listener: Http2.createSecureServer(sslDetails), // optional: remove this line for http1.1
host: 'localhost',
port: 3000,
tls: sslDetails,
routes: {
cors: true
},
})
console.log(`Host : ${server.info.host}`)
console.log(`Port : ${server.info.port}`)
console.log(`Method : ${server.info.protocol}`)
console.log(`Hapi : v${server.version}`)
server.route({
method: 'GET',
path:'/server/ping',
handler: async (request, reply) => {
console.log(`>>>ROUTE>>> : ${request.route.path}`);
const response = reply.response({ qSuccess: true, qResult: 'pong' })
return response;
}
})
server.start()
To reiterate, this code will "work", it will serve up a response if you load the /server/ping route in an independent way. If you were building a web server to serve pages, this would likely be sufficient to get going.
This code will still fail CORS validation in a web app. Why? Because the request to /server/ping is never even going to be made. The app will send a preflight OPTIONS request first. And there's nothing in this code to handle that. So nothing you do in the server.route area, messing with route options, or adding headers, is going to fix this. Ten jillion different setups in the main server instantiation of routes:cors wont fix this, because they also don't address the actual problem.
I added these, at the top of my middleware, to respond to options(pre-flight) request.
Might cause problems in other areas of the app that use the options, but worked for my case/issue.
async function(request: Request, h: ResponseToolkit) {
if (request.method === "options") {
const response = h.response({});
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "*");
return h.response(response).takeover();
}
// more checks....
},
The problem is there isn't a route set up that's dealing with the OPTIONS request that Chrome/Firefox will send before they attempt the GET/POST to the API being served up by Hapi. Add this code to the above file, just above server.start()
server.route({
method : 'OPTIONS',
path: '/{any*}',
handler : async (request, reply) => {
console.log(`======options: ${request.route.path}`)
const response = reply.response({})
response.header('Access-Control-Allow-Origin', '*')
response.header('Access-Control-Allow-Headers', '*')
return response;
}
})
Now you'll see that when you attempt to use the API from your app, it's going to make two calls: first, the options call, and then if that passes (and with these headers it now will) it will make the actual call you're interested in.
The "cors:true" route option, it really feels like it's going to "enable cors". I mean, yes... but really, no. Think of it more like it permits Hapi to do cors... you still have to do the work of "handling cors".

IIS Server hosting API and Angular 2 app No CORS sent

TLDR: 1 Server, IIS, two sites, missing CORS Headers on preflight response
> http://myserver:88 // This is my angular site
> http://myserver:8888 // This is my .NET CORE API
Background:
I've developed an ASP.NET CORE API and an Angular web application.
I have them hosted on the same server with IIS. They are two different 'Sites'.
I am having a CORS issue. I am force sending the response header like so....
Now in my login endpoint I need custom headers. So a preflight response is needed..
chrome shows this..
Then the very next call shows this.
I'm not sure what I'm doing wrong here...
This is how my cors set up looks in the API.
public void ConfigureServices(IServiceCollection services)
{
string[] origins = new [] { "http://myServer:88" }; // PORT TO HOSTED ANGULAR SITE
services.AddCors(options => {
options.AddPolicy("AllowSpecificOrigin", builder =>
builder.WithOrigins(origins)
.WithMethods("GET", "POST")
.WithHeaders("accept", "content-type", "origin", "username", "password")
.AllowCredentials()
);
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseCors("AllowSpecificOrigin");
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
There was nothing wrong with my CORS setup. It was due to an exception being thrown in my endpoint. I hope this helps others.

CORS on Web API and MVC 5 Controller: Upload images with fetch and FormData

I have an application that has the front and back ends running on different .NET projects.
The front end is an Aurelia web application running on ASP.NET 5. This Aurelia app (from now on The FrontEnd) gets all it's data from a Web API 2/MVC 5 application (henceforth, The BackEnd).
Since The FrontEnd and the BackEnd are different applications I have CORS setup, both for the Web API and in the Start.Auth.cs for the token bearer request.
The FronEnd is running on http://localhost:49850.
Now, for some code (this is all in the BackEnd)
Start.Auth.cs
The whole of the application resides behind a log-in form, so inside the Start.Auth.cs file, other than setting up the token-based authentication on the static Startup(), method I have a bit of middleware that adds the Access-Control-Allow-Origin header to the request on the one case where there is no token available yet: when we are requesting one.
public void ConfigureAuth(IAppBuilder app)
{
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.Equals("/token"))
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "http://localhost:49850" });
await next();
});
app.UseCors(CorsOptions.AllowAll);
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
WebApiConfig.cs
Here I just added the EnableCorsAttribute so that it is enable globally.
var enableCors = new EnableCorsAttribute("http://localhost:49850", "*", "*");
config.EnableCors(enableCors);
Uploading files
Everything works fine; I can perform GET and POST requests to the Web API without a problem, the problem comes when trying to upload images.
To upload to files I have an action method in an ASP.NET MVC controller called FileControler.
FileController.cs
[HttpPost]
public ActionResult UploadImage(string id, string name = "")
{
var files = (from string fileName in Request.File
select Request.Files[fileName]
into file
where file != null
select DoSomethingWithTheFile(file, id)).ToList();
// ...
return Json(arrayWithFileUrls);
}
Calling the MVC controller
This is already part of The FrontEnd.
To call this method I use Aurelia's Fetch Client:
upload(url, data, files) {
let formData = new FormData();
for (let key of Object.keys(data)) {
formData.append(key, data[key]);
}
for (let i = 0; i < files.length; i++) {
formData.append(`files[${i}]`, files[i]);
}
return this.http.fetch(url, {
method: "POST",
body: formData,
headers: {
cmsDatabase: sessionStorage["cmsDatabase"]
}
}).then(response => {
return response.json();
}).catch(error => {
console.log(error);
});
}
And here's a call to the upload method above:
// files comes from an <input type="file" />
upload("http://localhost:64441/file/uploadImage", { id: id }, files)
.then((uploadedPhotos) => {
// do something with those file urls...
});
The Problem
All this works if I remove all CORS setup from WebApiConfig.cs, and in Startup.Auth.cs I substitute the call to the middleware for app.UseCors(CorsOptions.AllowAll);, so I know my code is ok, but as soon as I use the CORS setup described above, everything works except the call to http://localhost:64441/file/uploadImage, returning even a 404:
Fetch API cannot load http://localhost:64441/file/uploadForSku.
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'
header is present on the requested resource.
Origin 'http://localhost:49850' is therefore not allowed access.
The response had HTTP status code 404. If an opaque response serves your needs,
set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
The "funny" thing is that if I try calling that url with, for intance, REST Console I don't get a 404.
I've tried adding the [HttpOptions] attribute to the action method; I've tried creating ActionFilterAttributes as described here and here, and even setting uip CORS from within the web.config, but to no avail.
I know the problem is that FileController is a regular MVC Controller instead of a Web API controlle, but shouldn't it still be possible to get CORS working?
have you tried this
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
in ApplicationOAuthProvider.cs file

Authenticate using ajax in Play 2.1

I'm writing a Play application where I would need the authentication to be also handled by another web application. So, when the user logs into the other web application it should also log into the Play application.
To implement security in Play I used the instructions in the Play Framework documentation: http://www.playframework.com/documentation/2.0.1/ScalaSecurity
My idea on how to do the external authentication, is to have the other application do an ajax call to log into the Play application, as I thought this would write the session cookie for the user. But this doesn't work. I still have to login manually when to the Play application.
Here is my controller:
val loginForm = Form(
tuple(
"username" -> nonEmptyText,
"password" -> nonEmptyText) verifying("Invalid email or password!", result => result match {
case (email, password) => Admin.authenticate(email, password)
}))
def jsLogin = Action {
implicit request => {
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(toJson("Unauthorized!")),
user => {
Ok(toJson("Ok")).withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> "*",
ACCESS_CONTROL_ALLOW_METHODS -> "POST",
ACCESS_CONTROL_MAX_AGE -> "300",
ACCESS_CONTROL_EXPOSE_HEADERS -> "Origin, X-Requested-With, Content-Type, Accept"
).withSession("email" -> user._1)
})
}
}
And here is the code I've used to test this:
$.ajax({
type: "POST",
url: "http://localhost:9000/jsLogin",
data: {
username: "username",
password: "password"
}
})
After debugging, I know that the jsLogin method works ok and it does log the user in, and the response get's ok to the ajax method. But when I try to access my play application it still asks me to log in manually.
Is there some non kludgy way to get the user logged in from the outside?
Ok, I got it to work. What I noticed is that the Set-Cookie header returned by the call was deemed Unsafe. To fix this I had to get the CORS headers correctly and send Credentials. So here is my new Query (used XmlHttpRequest instead of jQuery's ajax for debugging reasons, should work with ajax also).
var xmlhttp = new XMLHttpRequest();
var url = "http://localhost:9000/jsLogin";
var params = "username=username&password=password";
xmlhttp.open("POST", url, true);
xmlhttp.withCredentials = true;
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.send(params);
And here's the play controller
def jsLogin = Action {
implicit request => {
loginForm.bindFromRequest.fold(
formWithErrors => BadRequest(toJson("Unauthorized!")),
user => Ok(toJson("Ok")).withHeaders(
ACCESS_CONTROL_ALLOW_ORIGIN -> "http://sending.host.url",
ACCESS_CONTROL_ALLOW_METHODS -> "POST",
ACCESS_CONTROL_ALLOW_CREDENTIALS -> "true",
ACCESS_CONTROL_ALLOW_HEADERS -> "Origin, X-Requested-With, Content-Type, Accept").withSession("email" -> user._1)
})
}
}
So the fix was in having "withCredentials = true" on client side and on server side have the CORS headers set up correctly.
So you want to handle security by using Play's session cookie? Have you checked the cookie is actually there after the AJAX request? Is "the other" app on the same domain? If no, the cookie won't be used by the other app.
By the way, a better way to handle this is described in a blog post by James Ward.

Resources