I am using Angular 8, with old backend (ASP.NET MVC 5 Framework) NOT CORE
I am trying to send the cookies of the website so the request from the angular website considered authenticated
I created an interceptor for this
import { HttpInterceptor, HttpHandler, HttpEvent, HttpRequest }
from '#angular/common/http';
import { Observable } from 'rxjs';
import { Injectable } from '#angular/core';
#Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor() { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const newRequest = request.clone({
withCredentials: true,
});
return next.handle(newRequest);
}
}
here is the code of the request
private getDefaultConfiguration(): configurationsType {
return {
observe: 'response',
responseType: 'json',
headers: new HttpHeaders().set('Content-Type', 'application/json')
};
}
public async get(endpoint: string, params: HttpParams = undefined) {
const options = this.getDefaultConfiguration();
if (params !== undefined)
options.params = params;
const response: HttpResponse<object> = await this.http.get(endpoint, options).toPromise();
return await this.errorCheck(response);
}
I can confirm that the interceptor is executing by a console.log statement
the problem is that I am not receiving the cookies in the request (by the way Http Get not Post) and my request is considered to be unauthenticated.
I am using the following Filter for CORS problem
public class AllowCrossSiteAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpResponseBase response = filterContext.RequestContext.HttpContext.Response;
response.AddHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.AddHeader("Access-Control-Allow-Headers", "*");
response.AddHeader("Access-Control-Allow-Methods", "*");
response.AddHeader("Access-Control-Allow-Credentials", "true");
base.OnActionExecuting(filterContext);
}
}
I register the filter globally,
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AllowCrossSiteAttribute());
}
}
here is the cookie that I expect to be sent in the header, this snippet is from the login method
var ticket = new FormsAuthenticationTicket(SessionHelper.DefaultSession().KullaniciAdi, model.RememberMe, timeout);
string encrypted = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encrypted)
{
Expires = DateTime.Now.AddMinutes(timeout),
HttpOnly = true // cookie not available in javascript.
};
Response.Cookies.Add(cookie);
return RedirectToAction("Index", "Home");
and here is the cookie in the chrome
if you need any more information please ask me in the comment and I will provide that.
Update
I check it out this article and I applied the same-site attribute of the set-cookie to none, but this still does not solve the problem.
I updated the [AllowCrossSiteAttribute] to be like this, because of completely another problem I was receiving in angular
public class AllowCrossSiteAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpResponseBase response = filterContext.RequestContext.HttpContext.Response;
response.AddHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.AddHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
response.AddHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT");
response.AddHeader("Access-Control-Allow-Credentials", "true");
base.OnActionExecuting(filterContext);
}
}
In the OnAuthorization method which exists in the BaseController and which is called on each request, I tested the header of the request.
if I requested something from the old MVC application I receive the cookies correctly
but when I make a request from the angular project I receive no Cookies at all
here is how the cookies appear in chrome inspector
for the angular project
and for the old MVC project
Related
I am using a service to get user ids in HTML DropDownList and it works fine. Here am using an Angular service to get the user details and for back-end, ASP.NET Web Api. So far this is what I've done so far:
Web Api - C#:
[Route("api/values/GetUserInfo")]
[Authorize]
[HttpGet]
public List<User> GetUserInfo(string type)
{
List<User> lst = null;
if (type!= null)
{
lst = GetUserInfo().Where(c => c.userType== type).ToList();
}
else
{
lst = GetUserInfo().ToList();
}
return lst;
}
Angular: Service - UserService
GetUserInfo(dept: string) {
debugger;
this.Url = 'http://localhost:53743/api/values/';
var a = this.Url + 'GetUserInfo';
var headers_object = new HttpHeaders().set("Authorization", "Bearer " + localStorage.getItem('Token')); //Set JWT Token
let params = new HttpParams().set("type", type);
return this.http.get<any>(a, { headers: headers_object, params: params }); //Get request to retrieve the user details from database server
}
Finally in the Angular Component:
public empIds: any[];
constructor(private dataservice: UserService, private appComponent: AppComponent, private sanitizer: DomSanitizer, private route: Router, private http: HttpClient) { //UserService injected in the constructor
}
ngOnInit() {
this.LoadUserData('');
}
LoadUserData(dept: string) {
debugger;
this.dataservice.GetUserInfo(dept).subscribe(result => { //Calling the `Angular` service here
this.empIds = result; //Keeping the result set here
}, error => console.error(error));
}
So far, these are my done work and was wondering if I can manage the header initiated once in every HTTP request. Like a class that handles all the post and get request and I did some research on it searching goggle but bit confused how could I make it work accordingly? Below is what I've been studying so far and trying to implement in my code sample. But the issue is when there are parameters how I could handle them with the following code sample.
Angular - httpService:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class HttpClientService {
value: any;
constructor(private http: HttpClient) { }
createAuthorizationHeader(headers: HttpHeaders) {
//headers.append('content-type', 'application/json');
//headers.append("Authorization", "Bearer " + localStorage.getItem('Token'));
}
get(url, value, type) {
debugger;
let params = new HttpParams().set("dept", dept);
var headers_object = new HttpHeaders().set("Authorization", "Bearer " + value);
const httpOptions = {
headers: headers_object
};
return this.http.get<any>(url, {
headers: headers_object, params: params
});
}
post(url, data, value) {
let params = new HttpParams();
let headers_object = new HttpHeaders();
headers_object.append('content-type', 'application/json');
headers_object.append("Authorization", "Bearer " + value);
//this.createAuthorizationHeader(headers);
return this.http.post<any>(url, data, {
headers: headers_object, params: params
});
}
}
As an example below in the component:
this.httpService.get(a, localStorage.getItem('Token'), dept).subscribe(result => {
this.empIds = result;
}, error => console.error(error));
N.B: I am almost novice to Angular and it's working flow, trying to figure out the things in a better way - Thanks.
You should use interceptor.
If you want to add the specific headers in every request you have to do like the following : (Here I add Accept Header in every http request)
#Injectable()
export class ExampleAuth implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
request = request.clone({ headers: request.headers.set('Accept', 'application/json') }); // Here you can add your special headers
return next.handle(request);
}
}
then add the interceptor in the provider section of your app module as below :
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: ExampleAuth, multi: true }
],
bootstrap: [AppComponent]
})
export class AppModule { }
Now whenever a new HTTP request will be made, this Interceptor will modify the Headers of request.
More Explanations:
Angular Interceptor is a powerful feature which can be used in many ways for securing and handling many HTTP related phenomena.
What we can do using Interceptors?
Interceptors can be used in a number of ways in an application.
– Set request headers in HTTP calls like Content-Type or send any custom headers.
– Authenticate HTTP calls by setting Security tokens
– Show Spin Loaders/ Progress Bars as HTTPS calls are in progress.
– Handle Errors in HTTP calls at one place
– Show Response messages using Notifications/ Toasts
EDIT:
According to your example,In your HttpClientService , you have to remove the following lines because they will add automatically to the http request headers in the interceptor:
var headers_object = new HttpHeaders().set("Authorization", "Bearer " + value);
headers_object.append('content-type', 'application/json');
Modify your HttpClientService as below:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class HttpClientService {
value: any;
constructor(private http: HttpClient) { }
get(url, value, type) {
debugger;
let params = new HttpParams().set("dept", dept);
const options = {
params: params
};
return this.http.get<any>(url, options);
}
post(url, data, value) {
let params = new HttpParams();
const options = {
params: params
};
return this.http.post<any>(url, data, options);
}
}
Then in the Interceptor we have to add the headers that we have just removed from HttpClientService
#Injectable()
export class ExampleAuth implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
request = request.clone({ headers: request.headers.set('content-type', 'application/json') });
request = request.clone({ headers: request.headers.set("Authorization", "Bearer " + localStorage.getItem('Token') ) });
return next.handle(request);
}
}
Then register the interceptor in app module.
You can do it with HttpInterceptor which can intercept all your requests you make and all their responses.
You can thus also use it to display consistent error messages on each failed request.
You can have a look at Angular's documentation to see how to implement it.
The example the give is very similar to what you need:
import { AuthService } from '../auth.service';
#Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
// Get the auth token from the service.
const authToken = this.auth.getAuthorizationToken();
// Clone the request and replace the original headers with
// cloned headers, updated with the authorization.
const authReq = req.clone({
headers: req.headers.set('Authorization', authToken)
});
// send cloned request with header to the next handler.
return next.handle(authReq);
}
}
I have a regular application using cookie based authentication. This is how it's configured:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication("Login")
.AddCookie("Login", c => {
c.ClaimsIssuer = "Myself";
c.LoginPath = new PathString("/Home/Login");
c.AccessDeniedPath = new PathString("/Home/Denied");
});
}
This works for my regular actions:
[Authorize]
public IActionResult Users()
{
return View();
}
But doesn't work well for my ajax requests:
[Authorize, HttpPost("Api/UpdateUserInfo"), ValidateAntiForgeryToken, Produces("application/json")]
public IActionResult UpdateUserInfo([FromBody] Request<User> request)
{
Response<User> response = request.DoWhatYouNeed();
return Json(response);
}
The problem is that when the session expires, the MVC engine will redirect the action to the login page, and my ajax call will receive that.
I'd like it to return the status code of 401 so I can redirect the user back to the login page when it's an ajax request.
I tried writing a policy, but I can't figure how to unset or make it ignore the default redirect to login page from the authentication service.
public class AuthorizeAjax : AuthorizationHandler<AuthorizeAjax>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeAjax requirement)
{
if (context.User.Identity.IsAuthenticated)
{
context.Succeed(requirement);
}
else
{
context.Fail();
if (context.Resource is AuthorizationFilterContext redirectContext)
{
// - This is null already, and the redirect to login will still happen after this.
redirectContext.Result = null;
}
}
return Task.CompletedTask;
}
}
How can I do this?
Edit: After a lot of googling, I found this new way of handling it in version 2.0:
services.AddAuthentication("Login")
.AddCookie("Login", c => {
c.ClaimsIssuer = "Myself";
c.LoginPath = new PathString("/Home/Login");
c.Events.OnRedirectToLogin = (context) =>
{
// - Or a better way to detect it's an ajax request
if (context.Request.Headers["Content-Type"] == "application/json")
{
context.HttpContext.Response.StatusCode = 401;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
};
});
And it works for now!
What you need can be achieved by extending AuthorizeAttribute class.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new JsonResult
{
Data = new { Success = false, Data = "Unauthorized" },
ContentEncoding = System.Text.Encoding.UTF8,
ContentType = "application/json",
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
You can then specify this attribute on Ajax methods.
Hope this helps.
Reference: http://benedict-chan.github.io/blog/2014/02/11/asp-dot-net-mvc-how-to-handle-unauthorized-response-in-json-for-your-api/
According to this question I cannot immplement IHttpModule when self hsoting a web api on a windows service
Is there a way to add an httpModule when webApi is running with the HttpSelfHostServer?
However, I still need to add this code somewhere in my self hosted web api.
I found this blog about how to fix that:
http://www.silver-it.com/node/182
The code is as follows, however I can not have an IhttpModule implemented on aself hosted API
public class CORSPreflightModule : IHttpModule
{
private const string OPTIONSMETHOD = "OPTIONS";
private const string ORIGINHEADER = "ORIGIN";
private const string ALLOWEDORIGIN = "https://yourspodomain.sharepoint.com";
void IHttpModule.Dispose()
{
}
void IHttpModule.Init(HttpApplication context)
{
context.PreSendRequestHeaders += (sender, e) =>
{
var response = context.Response;
if (context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
{
response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
}
if (context.Request.HttpMethod.ToUpperInvariant() == OPTIONSMETHOD && context.Request.Headers[ORIGINHEADER] == ALLOWEDORIGIN)
{
response.Headers.Clear();
response.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.Headers.Add("Access-Control-Allow-Origin", "https://yourspodomain.sharepoint.com");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
response.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
response.Clear();
response.StatusCode = (int)HttpStatusCode.OK;
}
};
}
}
My self hosted web api is like this:
Program.cs
static void Main()
{
try
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new APIServiceTest()
};
ServiceBase.Run(ServicesToRun);
}
catch (Exception ex)
{
throw ex;
}
}
class Startup
{
// Hack from https://stackoverflow.com/a/17227764/19020 to load controllers in
// another assembly. Another way to do this is to create a custom assembly resolver
//Type valuesControllerType = typeof(OWINTest.API.ValuesController);
// This code configures Web API. The Startup class is specified as a type
// parameter in the WebApp.Start method.
public void Configuration(IAppBuilder appBuilder)
{
try
{
//Debugger.Launch();
// Configure Web API for self-host.
HttpConfiguration config = new HttpConfiguration();
config.MessageHandlers.Add(new CustomHeaderHandler());
var corsAttr = new EnableCorsAttribute(System.Configuration.ConfigurationManager.AppSettings["DominioSharePoint"].ToString(), "*", "*");
config.EnableCors(corsAttr);
// Enable attribute based routing
// http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
catch (Exception ex)
{
throw ex;
}
}
}
My controller:
[EnableCors(origins: "https://xx.sharepoint.com", headers: "*", methods: "*")]
public class CuentasCobroController : ApiController
{
However because its self hosted I cant implement an IHttpModule there as explained above, but I can create a custom handler how can I implemente the code above from the blog in the custom handler?
public class CustomHeaderHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith((task) =>
{
HttpResponseMessage response = task.Result;
response.Headers.Add("Access-Control-Allow-Origin", "*");
return response;
});
}
}
Question is, how can I integrate first code, into the startup of my windows service?
You are almost there by using DelegatingHandler instead of IHttpModule.
config.MessageHandlers.Add(new CorsHeaderHandler());
DelegatingHandler.SendAsync can access both request and response.
public class CorsHeaderHandler : DelegatingHandler
{
private const string OPTIONSMETHOD = "OPTIONS";
private const string ORIGINHEADER = "ORIGIN";
private const string ALLOWEDORIGIN = "https://yourspodomain.sharepoint.com";
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var allowedOrigin = request.Headers.Any(t => t.Key == ORIGINHEADER && t.Value.Contains(ALLOWEDORIGIN));
if (allowedOrigin == false) return task.Result;
if (request.Method == HttpMethod.Options)
{
var emptyResponse = new HttpResponseMessage(HttpStatusCode.OK);
emptyResponse.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
emptyResponse.Headers.Add("Access-Control-Allow-Origin", ALLOWEDORIGIN);
emptyResponse.Headers.Add("Access-Control-Allow-Credentials", "true");
emptyResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
return emptyResponse;
}
else
{
task.Result.Headers.Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
task.Result.Headers.Add("Access-Control-Allow-Headers", "Content-Type");
return task.Result;
}
});
}
}
Simply put, you can't use IHttpModule with self-hosted Web API, or anything that is not IIS. IHttpModule is an IIS concept only.
What you can do instead, as you have discovered, is you can modify the Web API pipeline and insert your code (or the Web API equivalent) there instead. This could be done with a DelegatingHandler, or an action filter.
The simplest solution, however, would be to simply use the Microsoft.AspNet.WebApi.Cors NuGet package. With an HttpConfiguration object, call .EnableCors(...) and pass in an EnableCorsAttribute object as per the instructions here from Microsoft.
This is what you've already done in your code above, but you seem to be trying to also add in the custom CORS code from your HTTP module. If you remove the EnableCors attribute from your controller, and remove your CustomHeaderHandler, it should work as you would expect.
I've got a project moving from MVC to WebApi 2. I have to occasionally set a cookie on the response from the webapi, so I wrote a class that extends OkNegotiatedContestResult,
public class OkWithCookiesResult<T> : OkNegotiatedContentResult<T>
{
IEnumerable<HttpCookie> cookies;
public OkWithCookiesResult(T content, IEnumerable<HttpCookie> cookies, ApiController controller)
: base(content, controller)
{
this.cookies = cookies;
foreach(var cookie in cookies)
{
cookie.Domain = Request.RequestUri.Host == "localhost" ? null : Request.RequestUri.Host;
cookie.HttpOnly = true;
cookie.Path = "/";
}
}
public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.ExecuteAsync(cancellationToken);
response.Headers.AddCookies(cookies.Select(c => c.ToCookieHeaderValue()));
return response;
}
}
This is the ToCookieHeaderValue method,
public static CookieHeaderValue ToCookieHeaderValue(this HttpCookie cookie) {
CookieHeaderValue val = new CookieHeaderValue(cookie.Name, cookie.Value);
val.Expires = cookie.Expires;
val.Domain = cookie.Domain;
val.HttpOnly = cookie.HttpOnly;
val.Path = cookie.Path;
val.Secure = cookie.Secure;
return val;
}
Some debug code is left over in there, but you get the gist. My problem is the cookies I add to the header are cleared when the request is finished. I figured this out because I subscribed to the Application_EndRequest event, and the final response there has only one cookie, .ASPXAUTH with a null value. Is this left over from some FormsAuthentication thing from MVC?
I have an ASP.NET MVC website using angular for the front end, that then needs to communicate to a REST Web API service to retrieve data. I have authentication logic against a custom provider in my MVC project and this all works fine using ADLDS. However the calls to the REST Web API have no authentication data passed and I can't work out how to pass the user who is authenticated with MVC, over to the REST Web API.
Here is an example call to the Web API.
public void ApproveModel(int modelVersionId, string comments)
{
var request = new RestRequest("api/modelversion", Method.POST) { RequestFormat = DataFormat.Json };
request.AddBody(new[] { new ModelAction { ModelActionType = ModelActionConstants.ModelActionApproveModel, ActionParameters = new Dictionary<string, string> { { ModelActionConstants.ModelActionParameterModelVersionId, modelVersionId.ToString(CultureInfo.InvariantCulture) }, {ModelActionConstants.ModelActionParameterComments, comments} } } });
request.UseDefaultCredentials = true;
var response = _client.Execute(request);
if (response.StatusCode != HttpStatusCode.OK)
throw new ServerException(response.Content);
}
My Web API Controller method (abbreviated)
[ValidateAccess(Constants.Dummy, Constants.SECURE_ENVIRONMENT)]
public HttpResponseMessage Post(ModelAction[] actions)
{
...
}
And my custom validate access attribute which uses Thinktecture
public class ValidateAccess : ClaimsAuthorizeAttribute
{
private static readonly ILog Log = LogManager.GetLogger(typeof(ValidateAccess));
private readonly string _resource;
private readonly string _claimsAction;
public ValidateAccess(string claimsAction, string resource)
{
_claimsAction = claimsAction;
_resource = resource;
XmlConfigurator.Configure();
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext == null) throw new ArgumentNullException("actionContext");
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
Log.InfoFormat("User {0} is not authenticated - Not authorizing further. Redirecting to error page.",
HttpContext.Current.User.Identity.Name);
return false;
}
// specified users or roles when we use our attribute
return CheckAccess(actionContext);
}
protected override bool CheckAccess(HttpActionContext actionContext)
{
if (_claimsAction == String.Empty && _resource == string.Empty)
{
//user is in landing page
return true;
}
return ClaimsAuthorization.CheckAccess(_claimsAction, _resource);
}
}
My problems are
I am not familiar with Web Api. Is IsAuthorized(HttpActionContext actionContext) the right method to override to enforce access policy on API calls?
Why am I getting null for the User identity?