Managing user authenticated state on manual URL navigation - authentication

I'm using Angular2 with ASP.NET Core MVC and managing manual URL navigation works fine, the server is loading my Home view with Angular2 successfully.
On user authentication, I'm setting up a session variable like this :
HttpHelper.HttpContext.Session.SetString("isLoggedIn", true.ToString() );
What I want is that after the user is in the application, if somewhat he wants to load a specific route by manually navigating to it, I want my service to call my ASP Controller to check if the user already got authenticated so that my guard allows the routing. Instead, the guard is by default set to false and I obviously get redirected to my login page.
Here is my ASP Controller method I want to call to update my IsLoggedIn value in my auth.service :
[HttpGet]
public IActionResult IsConnectedState()
{
if (!String.IsNullOrWhiteSpace(HttpHelper.HttpContext.Session.GetString("isLoggedIn")))
return Ok(true);
else
return Ok(false);
}
so that my AuthenticationGuard can call the AuthenticationService to update the boolean managing the authenticated state :
alert(this.authService.isLoggedIn);
if (!this.authService.isLoggedIn) {
this.authService.setupLoggedInState().subscribe(() => { });
}
with the following code updating the boolean in my auth.service :
setupLoggedInState() {
alert("Setting Up");
// Setting up the Http call
let lControllerAction: string = "/IsConnectedState";
let lControllerFullURL: string = this.controllerURL + lControllerAction;
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
// Call my ASP Controller IsConnectedState() method
return this.http.get(lControllerFullURL, options)
.map((res: any) => {
// Réponse reçue du WebService
let data = res.json();
alert(data);
if (data == true) {
this.isLoggedIn = true;
}
}
).catch(this.handleError);
}
When I do the authentication, and then manually navigate to an URL, my guard tells me that the boolean is indeed set to "false", and I also get the "Setting Up" when my service tries to call http.get.
It seems to bug in the middle of the method, because I never get to the breakpoint I set in my ASP Controller. I get the following error which I don't understand :
"platform-browser.umd.js:937 EXCEPTION: Error: Uncaught (in promise): TypeError: Cannot read property 'toString' of null"
Could it be because I don't call the service at the right moment in my auth.guard ? All my others call such as authentication work with http.post without any issue, so I don't really get where the problem is coming from...
Any help would be greatly appreciated.

There's a known defect in Angular 2 RC5 that causes this error when you do a get and provide the 'Content-Type': 'application/json' header.
For a temporary workaround, add an empty string to the body property on your request options:
let options = new RequestOptions({ headers: headers, body: "" });

Related

How to call StateHasChanged() / NotifyAuthenticationStateChanged from a class in Blazor WASM

In my project I created a class to handle http POST. The main method in the class first check if a JWT Token is available in local storage, check exp date, decide if a new toekn is needed and if so use the refresh token and finally do the POST.
I inject this class with builder.Services.AddTransient<IMyHttp, MyHttp>();
Now I would like to notify the UI (StateHasChanged() or NotifyAuthenticationStateChanged) in case the refresh token is not valid so to log out the user immediately.
The point is that I do not know how to raise the event from my http class (while from a controller is just a matter of calling this.StateHasChanged()).
As suggested here you are the (pseudo)code:
Index controller call the WebAPI to check weather:
(HttpResponseMessage, IEnumerable<WeatherForecast>) result = await MyHttp.PostPFAsync<IEnumerable<WeatherForecast>>("WeatherForecast/GetWeather", null);
This is MyHttp.PostPFAsync injected with builder.Services.AddTransient<IMyHttp, MyHttp>(); in Program.cs
public async Task<(HttpResponseMessage, T)> PostPFAsync<T>(string requestUri, object data)
{
// I get my JWT Token from localstorage, set up auth headers, create StreamContent content serializing data and then
HttpResponseMessage response = await _http.PostAsync(requestUri, content);
if (response.IsSuccessStatusCode)
{
return (response, "Happiness");
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
// Here I clear tokens from localstorage and I'd also like to logout user and update UI
}
else return (response, default(T));
}
Obviosuly I could logout and update the UI in the Index controller but this would mean to make the check everywhere I call the WebAPI via MyHttp.PostPFAsync while I would like to centralize it as soon as I get a 401 (actually I'll do this if I fail to use the refresh token but keep things simple in this example).

Response to preflight request doesn't pass access control check: It does not have HTTP ok status. GET working POST PUT DELETE not working

Greetings
I have one web application with following architecture:
Web api: ASP.net core 2.1 (Windows Authentication)
UI: angular 8
UI is able to get data but unable to send data.
I mean GET method is working fine but POST, PUT, DELETE options are not working .
And all the methods are working using POSTMAN.
ERROR is:
Access to XMLHttpRequest at 'http://xx.xxx.xxx.xx:xxyy/xxx/xxxxxx/Method' from origin 'http://localhost:xxxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Any help will be appreciated .
Thanks in advance :)
That's because your API is on different domain than your SPA angular application.
Please at this at the start of your Configure method in Startup.cs
if (env.IsDevelopment())
{
app.UseCors(opts =>
{
opts.WithOrigins(new string[]
{
"http://localhost:3000",
"http://localhost:3001"
// whatever domain/port u are using
});
opts.AllowAnyHeader();
opts.AllowAnyMethod();
opts.AllowCredentials();
});
}
Please note that this will handle only CORS for local development since you'll probably have same domain in production - if not, you'll need to reconfigure this for production also.
CORS blocking is browser specific and that's why it's working in PostMan but not in browser.
This is what i use and it should work i hope for your case.
My startup.cs ConfigureServices() decorated with:
services.AddCors(feature =>
feature.AddPolicy(
"CorsPolicy",
apiPolicy => apiPolicy
//.AllowAnyOrigin()
//.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed(host => true)
.AllowCredentials()
));
And, Configure() method with:
app.UseCors("CorsPolicy");
Notice the SetIsOriginAllowed() and allowCreds() along with other policy settings, this works for me with POST calls to my api from my angular, which are running on two different port#s.
UPDATE:
Following the questions on the comments, adding additional information on how do we check the logged in user (windows auth) btwn api and the angular (frontend).
You can check the incoming User on a specific route that would only expect the authenticated user using the decoration [Authorize]. In my case, i would have only one method that would expect the windows user in the api:
[HttpGet("UserInfo")]
[Authorize]
public IActionResult GetUserInfo()
{
string defaultCxtUser = HttpContext?.User?.Identity?.Name;
if (defaultCxtUser != null && !string.IsNullOrEmpty(defaultCxtUser))
{
_logger.LogDebug($"START - Get Context user details for {defaultCxtUser}");
ADHelper.logger = _logger;
var userFullName = ADHelper.GetUserIdentityInfo(defaultCxtUser);
_logger.LogInformation($"Context user {defaultCxtUser} with name: {userFullName}");
var userInfo = new { Name = userFullName };
//_logger.LogDebug($"END - GetUserInfo({defaultCxtUser} for {userFullName}");
return Ok(userInfo);
}
else
return Ok(new { Name = defaultCxtUser });
}
then i would call this from my angular with the service call as,
// Get the Logged in user info
GetCurrentUserInfo(): Observable<string> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true
};
// return this.http.get<string>(`${ApiPath}UserInfo`, httpOptions)
// .pipe(map(v => v as string));
return this.http.get<UserInfo>(`${ApiPath}UserInfo`, httpOptions)
.pipe(map(data => {
// console.log(data, data.Name);
return data.Name;
}))
;
}
Please see the headers with 'withCredentials: true' line that would trigger to pass the current user info, and it would be read and understood only if it has the authorize attr to read the User.Identity object in c# side. The reason we do this on a specific method is that, there should be some other parental method in the api like ApiStatus() or anything that could be, should be called first. This would ensure to also invoke the preflight check with OPTIONS that would require anonymous auth. Like in my case, getting whether the api is available and running, and some other app environment info before i get the userInfo() from my angular app.

405 Response on post using axios in Vue 3.0 app to .Net Core 2.2 with AllowAnyMethod in policy

I am losing my mind and having read a lot of blogs, SO questions and documents I am sure it is a simple fix that I am now completely blind to.
I have an axios post call from a vuejs app to a .net core 2.2 api project. Following the Enable CORS guide from Microsoft I have used Access Policy in Services and decorated the controller. See the code below.
The pre-flight options response is 204 with a 405 response on the actual call citing allow: DELETE, GET, PUT as the permitted method ... what have I missed here? I have .AllowAnyMethod in the policy but it seems to be completely ignored. A colleague working with a WebAPI 2.2. project has the exact same code and it works.
StartUp.cs
services.AddCors(options =>
{
options.AddDefaultPolicy(
builder =>
{
builder.WithOrigins("http://example.com",
"http://www.contoso.com");
});
options.AddPolicy("VueFrontEnd",
builder =>
{
builder.WithOrigins("http://localhost:30001/")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Controller
[EnableCors("VueFrontEnd")]
[HttpPost]
public async Task<JsonResult> DoesItExist(string searchString)
{
var data = new string[] { "A", "B", "C" };
var result = data.Any(searchString.Contains);
return Json(JsonConvert.SerializeObject(result));
}
Vue
getClientsByPartialString(search: string) {
Axios({
method: 'post',
url: 'https://localhost:44380/api/values/DoesItExist',
crossDomain: true,
data: {
name: 'world',
},
})
}
This makes me sad. It was routing.
Adding [HttpPost("/TheApi")] decorator sorted it.
I am ashamed. I was using the full URL http://localhost:port/api/values/themethod and routing was failing me despite it being set on the controller.
The one thing that concerns me is why this worked with GET and PUT and ONLY failed on POST. I have no answer to that one.

page Redirect in ASP.Net MVC + Web Api + AngularJs

I am building a ASP.Net MVC application that can work both in Web and JQuery mobile. So i am creating a seperate view for Web and JQuery mobile application. I have placed all my primary business logic services as a Web Api calls which are called by both the clients using the AngularJs which is working fine so far.
Now I was looking to introduce the security in to the application, and realized that Basic authentication is the quickest way to get going and when I looked around I found very nice posts that helped me build the same with minimal effort. Here are 3 links that I primarily used:
For the Client Side
HTTP Auth Interceptor Module : a nice way to look for 401 error and bring up the login page and after that proceed from where you left out.
Implementing basic HTTP authentication for HTTP requests in AngularJS : This is required to ensure that I am able reuse the user credentials with the subsequent requests. which is catched in the $http.
On the Server Side :
Basic Authentication with Asp.Net WebAPI
So far so good, all my WebApi calls are working as expected,
but the issue starts when I have to make calls to the MVC controllers,
if I try to [Authorize] the methods/controllers, it throws up the forms Authentication view again on MVC even though the API has already set the Authentication Header.
So I have 2 Questions:
Can We get the WebApi and MVC to share the same data in the header? in there a way in the AngularJS i can make MVC controller calls that can pass the same header information with authorization block that is set in the $http and decode it in the server side to generate my own Authentication and set the Custom.
In case the above is not possible, I was trying to make a call to a WebApi controller to redirect to a proper view which then loads the data using the bunch of WebApi calls so that user is not asked to enter the details again.
I have decorated it with the following attribute "[ActionName("MyWorkspace")] [HttpGet]"
public HttpResponseMessage GotoMyWorkspace(string data)
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper";
}
else if (System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter";
}
var response = Request.CreateResponse(HttpStatusCode.MovedPermanently);
string fullyQualifiedUrl = redirectUrl;
response.Headers.Location = new Uri(fullyQualifiedUrl, UriKind.Relative);
return response;
}
and on my meny click i invoke a angular JS function
$scope.enterWorkspace = function(){
$http.get('/api/execute/Registration/MyWorkspace?data=""')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
i see in the chrome developer tool that it gets redirected and gets a 200 OK status but the view is not refreshed.
is there any way we can at least get this redirect to work in case its not possible to share the WebApi and MVC authentications.
EDIT
Followed Kaido's advice and found another blog that explained how to create a custom CustomBasicAuthorizeAttribute.
Now I am able to call the method on the Home controller below: decorated with '[HttpPost][CustomBasicAuthorize]'
public ActionResult MyWorkspace()
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper/";
}
else if(System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter/";
}
return RedirectToLocal(redirectUrl);
}
Again, it works to an extent, i.e. to say, when the first call is made, it gets in to my method above that redirects, but when the redirected call comes back its missing the header again!
is there anything I can do to ensure the redirected call also gets the correct header set?
BTW now my menu click looks like below:
$scope.enterMyWorkspace = function(){
$http.post('/Home/MyWorkspace')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
this finally settles down to the following URL: http://127.0.0.1:81/Account/Login?ReturnUrl=%2fshipper%2f
Regards
Kiran
The [Authorize] attribute uses forms authentication, however it is easy to create your own
BasicAuthenticationAttribute as in your third link.
Then put [BasicAuthentication] on the MVC controllers instead of [Authorize].

Communication between AngularJS and a Jersey Webservice which are on a different domain. Can't access correct session

Lately I've been playing around with AngularJS and Java EE 6. I've build an webservice with Jersey and deployed the project on Glassfish. Because I needed some kind of authentication and an OAuth implementation or an JDBCRealm seemed overkill I decided to just create a session if the user successfully logged in.
#POST
#Path("/login")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
public Response login(LoginDAO loginData, #Context HttpServletRequest req) {
req.getSession().invalidate();
loginData.setPassword(PasswordGenerator.hash(loginData.getPassword()));
User foundUser = database.login(loginData);
if(foundUser == null) {
return Response.status(Status.CONFLICT).build();
}
req.getSession(true).setAttribute("username", foundUser.getUsername());
return Response.ok().build();
}
#GET
#Path("/ping")
public Response ping(#Context HttpServletRequest req) {
if(req.getSession().getAttribute("username") == null) {
return Response.ok("no session with an username attribute has been set").build();
}
return Response.ok(req.getSession(true).getAttribute("username")).build();
}
This seems to work alright, if I post to /login from Postman or from a basic jQuery webpage deployed on glassfish I do get the correct username back and a session has been placed. If I then send a GET request to /ping I do get the username back from which I logged in.
I've an AngularJS application deployed on a node.js webserver which needed to login. Because this server is on another port its on another domain and I had to go through the pain of enabling cors. I did this by building a container response filter which sets the response headers.
public class CrossOriginResourceSharingFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest creq, ContainerResponse cresp) {
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost:8000");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Credentials", "true");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
return cresp;
}
}
This did made it possible for me to send different types of HTTP requests from AngularJS to Java EE 6 application deployed on glassfish.
The problem is that when I send a POST request from AngularJS to the /login method, a session is created and I do get my username back. But when I send a GET request to the /ping method I get the "no session with an username attribute has been set" notice.
I believe this has to do with cross domain prevention and that I've to set the withCredentials tag when I send a xhr request. I've been trying to do this in AngularJS but haven't found out how to do this.
function LoginCtrl($scope, $http) {
$scope.login = function() {
$http.post("glassfish:otherport/api/login", $scope.credentials).
success(function(data) {
console.log(data);
}).
error(function(data, error) {
console.log(error);
});
};
};
And in another controller:
$scope.getUsername = function() {
$http.get("glassfish:otherport/api/ping", {}).
success(function(data) {
$scope.username = data;
}).
error(function() {
$scope.username = "error";
})
}
I've tried to set withCredentials is true
$http.defaults.withCredentials = true;
This however didn't solve my problem. I also tried to send it with every request in the config parameter but this didn't solve my problem either.
Depending on the version of AngularJS you are using you might have to set it on each $http.
Since 1.2 you can do:
$http.get(url,{ withCredentials: true, ...})
From 1.1.1 you can globally configure it:
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
If you're using an older version of Angular, try passing a config object to $http that specifies withCredentials. That should work in versions before 1.1:
$http({withCredentials: true, ...}).get(...)
See also mruelans answer and:
https://github.com/angular/angular.js/pull/1209
http://docs.angularjs.org/api/ng.$http
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#section_5
just an update to #iwein anwser, that we can now set in config itself
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
https://github.com/angular/angular.js/pull/1209
(available only after unstable version: 1.1.1)
In 1.2 version, this doesn't work for me:
$http({withCredentials: true, ...}).get(...)
if I read the doc, the shortcut method should take the config object
$http.get(url,{ withCredentials: true, ...})
$http is a singleton, That's the only way to mix in a same application requests with and without credentials.