-
Notifications
You must be signed in to change notification settings - Fork 433
XSRF token and SSR #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
With the following I get the proper headers sent back to the server from angular automatically. Not sure how you're implementing it. But bear in mind using his provided http-transfer wrapper, it will pass null options to http calls if you don't supply them manually, thus preventing any automatic usage of any defined BaseRequestOptions service interceptor you might have defined to update headers. Startup.cs configuring services: services.AddAntiforgery(opts => opts.HeaderName = "X-XSRF-Token");
services.AddMvcWithDynamicApi<DbEntities>(options =>
{
options.Filters.AddService(typeof(AntiforgeryCookieResultFilter));
});
services.AddTransient<AntiforgeryCookieResultFilter>(); AntiforgeryCookieResultFilter.cs: public class AntiforgeryCookieResultFilter : ResultFilterAttribute
{
private readonly IAntiforgery _antiforgery;
public AntiforgeryCookieResultFilter(IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ViewResult)
{
var tokens = _antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
}
}
} |
Uhm, the asp net side works fine. It issues the token!
How did you get the angular Http service to read the cookie and send it as
header?
…On Tue, 13 Jun 2017 at 21:28, Kyse ***@***.***> wrote:
With the following I get the proper headers sent back to the server from
angular automatically. Not sure how you're implementing it. But bear in
mind using his provided http-transfer wrapper, it will pass null options to
http calls if you don't supply them manually, thus preventing any automatic
usage of any defined BaseRequestOptions service interceptor you might have
defined to update headers.
Startup.cs configuring services:
services.AddAntiforgery(opts => opts.HeaderName = "X-XSRF-Token");
services.AddMvcWithDynamicApi<DbEntities>(options =>
{
options.Filters.AddService(typeof(AntiforgeryCookieResultFilter));
});
services.AddTransient<AntiforgeryCookieResultFilter>();
AntiforgeryCookieResultFilter.cs:
public class AntiforgeryCookieResultFilter : ResultFilterAttribute
{
private readonly IAntiforgery _antiforgery;
public AntiforgeryCookieResultFilter(IAntiforgery antiforgery)
{
_antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ViewResult)
{
var tokens = _antiforgery.GetAndStoreTokens(context.HttpContext);
context.HttpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
}
}
}
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#303 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABmDdqwGB1AW87HLQJqoDOygFlvicluEks5sDtTVgaJpZM4N4wAu>
.
|
Per Angular.io Guide Security -> XSRF it takes care of it automatically.
Double check you're using XSRF-TOKEN as the cookie name, and X-XSRF-TOKEN as the header it looks for. My guess is, if this works fine on the browser -> api side, and your only experiencing issues with SSR calls to the API, you could add in a BaseRequestOptions to check if the platform is server, and manually add in the X-XSRF-TOKEN header from a transferData value. You'd have to grab the token in the HomeController and inject it into the transfer data and make sure it all gets wired up properly for the server side of things. I haven't tested with POST, but GET's on SSR doesn't give me any issues regarding XSFR. |
Yes, that's what I thought so too. I understand it conceptually but I'm too new in Angular to understand your steps - would you please give a few more pointers, like where I would do each thing? :) |
Frankly, whether using the http or transfer-http modules should all be including the header automatically no matter which platform is calling them. Can you post your implementation? For manually intercepting and modifying http headers: In the modules/transfer-http/ folder create a new file: transfer-http-options.tsCreate an interceptor for http requests to modify headers. import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { BaseRequestOptions, RequestOptions } from '@angular/http';
import { REQUEST } from '../../app/shared/constants/request';
import { TRANSFERDATA, TransferData } from '../../app/shared/constants/transferData';
@Injectable()
export class TransferHttpOptions extends BaseRequestOptions {
constructor(
@Inject(REQUEST) private request,
@Inject(PLATFORM_ID) private platformId: Object,
@Inject(TRANSFERDATA) private transferData: TransferData
) {
super();
this.headers.append('Authorization', "bearer " + this.transferData.apiToken);
if (isPlatformServer(PLATFORM_ID)) {
console.log('Add stuff to http request header for server side only here.');
}
}
}
export const requestOptionsProvider = { provide: RequestOptions, useClass: TransferHttpOptions }; transfer-http-module.tsAdd interceptor to module import { NgModule } from '@angular/core';
import { TransferHttp } from './transfer-http';
import { TransferHttpOptions } from './transfer-http-options';
@NgModule({
providers: [
TransferHttp,
TransferHttpOptions
]
})
export class TransferHttpModule {} transfer-http.tsUpdate service to use interceptor. Note: Need to do this because interceptor isn't used automatically when this service passes a null options object to http calls. // Add import
import { TransferHttpOptions } from './transfer-http-options';
// Update constructor to inject our interceptor.
@Injectable()
export class TransferHttp {
private isServer = isPlatformServer(this.platformId);
constructor(
@Inject(PLATFORM_ID) private platformId,
private http: Http,
private transferOptions: TransferHttpOptions,
protected transferState: TransferState
) { }
// Update calls to http to use interceptor if options is null.
get(url: string, options?: RequestOptionsArgs): Observable<any> {
return this.getData(url, options, (url: string, options: RequestOptionsArgs) => {
return this.http.get(url, options || this.transferOptions);
});
} |
So after further investigation, it appears there are some issues with the xsfr token. Initial page load, token is generated page is sent to user. User logs in via post, and auth service is updated, and redirected to main page (this is all handled in the SPA). When I initially setup my auth testing with a seperate mvc page/form to handling login/logout, it seemed to work properly, but moving everything inside the SPA to handle presenting the login/logout form/pages, the xsfr token isn't updated once authentication happens. I get an error that the token was generated for a different principal. This would obviously also happen if the user's rolers change during being logged in and (via api for instance) and the validate principal authentication validator class replace the principal cookie with an updated principal respecting the changed roles, or whatever the validator is setup to detect is out of date. Basically somethings missing here. Any time the principal changes, the xsfr token needs to be updated on the browser. But this isn't happening with regular http calls. Going to experiment with using transferHttp wrapper calls to see if that properly gets the updated tokens set on the browser. I would have expected End of the day, until this is resolved, a browser refresh is required to get HMR and any api calls to work after a sign in, or to sign in again after a sign out happens. Because the antiforgery token is generated using the user principal context it seems and when that changes, it barfs. |
Ok, so I reworked my setup a little. Instead of completely handling login/logout responses inside angular based on the response from the login/logout account controller actions, I had the response return a redirect to returnurl or "/" if successfull. This properly gets the xsrf token updated on the browser linked to the logged in identity context. I noticed an oddity with my JWT bearer token however doing it like this. @MarkPieszak, perhaps you could shed some light here. As you suggested in another ticket, I'm passing the JWT in TransferData to the angular app so it can properly do server side rendering when API calls are needed. So I noticed on the browser side after the login redirect, the response from the redirected url (in this case "/") gives me the proper updated jwt token in the transferdata script tag. Initial Login: <script>window['TRANSFER_CACHE'] = {"isAuthenticated":false,"userName":null,"apiToken":"unauthenticated_token"};</script> Check Chrome console:
Log in response: Redirect to / <script>window['TRANSFER_CACHE'] = {"isAuthenticated":true,"userName":"kyse","apiToken":"autheneticated_token"};</script> Check Chrome console after login/redirect reloads page:
When I made an api call (hit a SPA page that calls out to the API), I end up with the unauthenticated_token being used. I checked the console both before and after login and noticed it's not updated, yet it's returned with the redirect html/script content. using Cache-Control:no-cache, so that's not the issue. |
I'm using the anti-forgery package like you've outlined above and then this simple middleware:
I have to say I'm really frustrated with angular. Even at version 4 the ecosystem doesn't seem all that mature and there are hurdles all the way. Me and my team at the time adopted AngularJS (v1) when it was at 0.9.x or so and we loved working with it over time. Not getting the same feeling with ng2/4 and I'm not sure it's because it's new. I'm gonna give React a try. |
@georgiosd Do you need Server-side rendering? Have you thought about removing SSRendering, you won't run into nearly as many issues? Reading through everything now, is the problem adding the correct Headers to Http calls, or transfering them to the client? Also check out this comment here, see if it's similar to how you're doing things now: |
I'm kind of confused by this thread my apologies. Is the issue simply doing JWT auth in Angular against some provider on the backend? I have a simple user table that is a copy of the identity one. I can register, login, and logout in Spa passing a JWT around. I can share my code if someone is interested. |
Can you post some of it just in case I'm sure others will run into similar issues! |
@MarkPieszak I think that's a little of a moot point (whether I need it right now or not). I mean, I could live without it but it would need a lot of extra effort to get the site online and kind of defeats the point of switching to ng2 - universal was one of the main selling points for me because it made SEO so much easier. Anyway - don't mean to be a party pooper. I'm just disappointed that I'm disappointed :) @isaac2004 it needs to be with cookies! |
I totally understand, I'm trying to help get edge case scenarios into documentation or this repo (somehow) where possible, but it's tough to get to them all, there's just a million scenarios! 🐰 |
I understand, and salute everyone's efforts. React had more time on this so I guess it's reasonable to be a bit ahead. Perhaps the separation of scenarios as I suggested in another thread could help you with the million combos :) |
Sorry, I did kind of hijack it with JWT. My biggest issue with it is setting up services that need data to use the constructor to grab the jwt token for making api calls, then on the login/logout side to update the jwt token. Doing a transferhttp.get does not update the jwt token in tranferData global variables unless you do a browser refresh. |
@georgiosd have you given this a try with Dot Net Core 2.0? Shawn Wildermuth has a pretty straightforward walk through on doing Cookie Auth. Is the issue you are having on the Angular side? |
Can you please share the code? |
@naveedahmed1 when I get some spare time, I am going to create a branch or PR with end to end Auth with JWT |
Thank you so much @isaac2004 . A suggestion regarding this repo, the current project has alot of external plugins and other stuff, I understand that its helpful for many people. But I think not everyone is using ng-bootstrap or Signal R etc. Shouldn't we have a repo with a much simplified version, which just shows using ASP.Net Node Services and Angular Universal along with SEO benefit? |
@isaac2004 I think that is the plan more or less. This repo will change a lot over the next few months |
Sounds great, I hope it could be done soon :) |
please take a look at the howto/auth branch for end to end JWT flow. Open a new issue if you have questions |
@isaac2004 I've had a look at that branch, is there a way to rework this to work with Identity Server Cookie Auth / OIDC? |
@kyl3t pretty sure identity server doesn't have support for Core 2.0 yet DuendeArchive/IdentityServer4#1173 I can put in simple cookie auth though. |
@isaac2004 I'm still using the .NET Core 1.1 version for this repo because of that very reason. I've managed to hook up my app as an Identity Server client for the server pages. And the REST API calls work because the cookies are sent back to server. My main issue is that when the Identity timeout expires, it tries to redirect my browser to my identity server (when I do API calls) resulting in a failed 302 due to cross-domain. Any architectural recommendations are welcome. |
@kyl3t hmmmm that is interesting. The auth branch target core 2.0 so most of that code is worthless to you (the server stuff at least). Are you able to catch the identity server exception in anyway? If yes you can log and do whatever. Sorry if I am missing a key piece. |
Welp! My head hurts!
So you know how angular is supposed to read
XSRF-TOKEN
cookies and pass them aX-XSRF-TOKEN
header when making a HTTP request?Well, that doesn't seem to work with universal. I tried manually defining the
XsrfStrategy
inapp.module
but it complained about cookies.Any ideas??
The text was updated successfully, but these errors were encountered: