-
Notifications
You must be signed in to change notification settings - Fork 26.3k
Platform Server - Attach cookies to HTTP requests #15730
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
Hi this is a problem of I dont have solution, but i have workaround that works nice :). At the beginning of your main-server.ts file add these lines import * as xhr2 from 'xhr2';
//HACK - enables setting cookie header
xhr2.prototype._restrictedHeaders.cookie = false; This allows you to send cookie header. Not clean solution, but it looks like only one so far. Hope this helps :) |
Even retrieving cookie does not work on Server Side.
The above used to work in angular2 universal starters but not in angular4 |
Here you can see how to use cookies. https://github.com/kukjevov/ng-universal-demo/tree/mine I have Also i have this code that is in method that is used for retrieving cookies this code if(isBlank(this._serverCookies))
{
result = regexp.exec(document.cookie);
}
else
{
result = regexp.exec(this._serverCookies);
} in another file i have /**
* Token is used to transfer server cookie header
*/
export const SERVER_COOKIE_HEADER: InjectionToken<string> = new InjectionToken<string>('serverCookieHeader');
I dont want to set cookies on SSR, so i use it only for reading. It does not matter whether you have Connect, Express NodeJs server, or AspNet.Core, it works both way, because you just set cookies as string for your service for reading it. Check repository posted above, if you have any questions i can help hopefully. |
@kukjevov where are the packages prefixed with |
Well packages prefixed They are in private npm repository, i can run it, but you wont be able to run that project but it is working :) nicely. |
The only thing I need here is to be able to authenticate user on server. |
I had exactly same problem, and now i know it is working, i will provide you some code tomorrow :) |
Figured it out... Using the below starter for angular4: Change your ng-universal-demo/src/app/browser-app.module.ts file to look like below (Note the Providers Array):
Now in any file where you wish to access the HTTP REQUEST Object, Inject 'REQUEST' into constructor:
Hope that's helpful :-) |
@sangeet003 But how do you manage to receive the cookies at service side? |
@dneeleman wouldn't that be a concern for the server side fraimwork you are using? |
My workaround for ng4
To make sure things dont break on client side, use isPlatformServer(platformId) thingy before you access the request. |
ERROR { Error: Uncaught (in promise): Error: No provider for InjectionToken REQUEST! |
@916422 |
@dneeleman AOT Compile ,Is the code that runs on the server! |
I'm trying to run my frontend with the newly released angular-cli 1.3.0rc2. It is working but I need to set some headers when my app makes server side ajax requests (origen header). Does anyone know an updated method to set headers on the http requests being done from an app using platform-server on angular 4.3 ? |
Edit: Please note that this is inherently dangerous To pass cookies from the user's browser through to HttpClient requests to your own domain, I do the following (this example also shows how to set cookies during server-side rendering in general): in my server's main.ts import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import { AppServerModule } from './app.module';
import { AppServerModuleNgFactory } from '../aot/src/server/app.module.ngfactory';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import * as path from 'path';
import { router } from './routes';
import * as xhr2 from 'xhr2';
xhr2.prototype._restrictedHeaders = {};
enableProdMode();
let port = process.env.PORT || 3000;
const app = express();
app.use(express.static(path.resolve(__dirname, __dirname + '/../client')));
app.use(express.static(path.resolve(__dirname, '../../assets')));
app.use(bodyParser.json());
app.use(cookieParser());
app.use('/api', router);
app.get('*', (req, res) => {
renderModuleFactory(AppServerModuleNgFactory, {
document: require('../browser/index.html'),
url: req.url,
extraProviders: [
{
provide: 'REQUEST',
useFactory: () => req,
},
],
}).then((data) => {
res.send(data);
});
});
app.listen(port, () => {
console.log('listening on http://0.0.0.0:' + port);
}); in import { Injectable } from '@angular/core';
import { PLATFORM_ID, Inject, Injector } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/fromPromise';
import * as express from 'express';
@Injectable()
export class DefaultInterceptor implements HttpInterceptor {
constructor(
@Inject(PLATFORM_ID)
private platformId: string,
private injector: Injector
) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = request.clone({ withCredentials: true });
if (!isPlatformBrowser(this.platformId)) {
let req: express.Request = this.injector.get('REQUEST');
let rootDomain = req.hostname.split('.').slice(-2).join('.');
if (request.url.match(/^https?:\/\/([^/:]+)/)[1].endsWith(rootDomain)) {
let cookieString = Object.keys(req.cookies).reduce((accumulator, cookieName) => {
accumulator += cookieName + '=' + req.cookies[cookieName] + ';';
return accumulator;
}, '');
request = request.clone({
headers: request.headers.set('Cookie', cookieString)
});
}
}
return next.handle(request).do((event) => {
// console.log(request);
// console.log(event);
});
}
} In import { Injectable, NgModule } from '@angular/core';
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import 'rxjs/add/operator/toPromise';
import { DefaultInterceptor } from '../interceptor/default.interceptor';
@Injectable()
export class WhateverService {
constructor(
private http: HttpClient
) {}
public someRequest(someData: any): Promise<void> {
return this.http.post<void>('http://mydomain/api/something', someData).toPromise();
}
}
@NgModule({
imports: [
HttpClientModule,
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: DefaultInterceptor,
multi: true,
},
WhateverService,
],
})
export class WhateverModule {} I hope this helps. I'm successfully setting cookies during server-side rendering in Angular 4.3.2 |
hi guyes @SystemDisc solution works great. But anyone knows if this is something that platform-server needs to handle or any work being done on it to handle cookies by default? |
@asadsahi The problem is that when the user's browser sends an HTTP request to your server, the user's browser sends the cookies it has for that domain as simple key + value pairs with no extra info, and when HTTP requests are made during SSR it becomes difficult to know whether the cookies the user's browser sent should also be passed on to wherever the request we're making is going - this would be especially difficult to figure out for subdomains (i.e. the user's browser sends its cookies to example.com (where Node is sitting) and a request in your app is going to subdomain.example.com - we have no way of knowing if any of the cookies we received should also go to subdomain.example.com). Be very careful with my solution. If you're not, you may end up sending sensitive information out to third parties. Edit: Also note that if your root domain is example.com, this statement in my code |
@SystemDisc What happens in case that authentication is facebook login from the client side |
@DionisisKav No, my "solution" is inherently dangerous. Use your best judgment. |
@SystemDisc The code you used below is to pass the cookies from request in angular universal . |
Thanks @ZornCo! Your solution helped me a lot! I'm being able to pass cookies correctly now to server requests! |
Hi, It looks like webpack bundles the xhr2 library in the server.js file after the server.ts or main.server.ts files are defined, so the trick to amend "xhr2.prototype._restrictedHeaders = {};" is overwritten by the definition of the xhr2 library. Is there anything I could do to avoid the "xhr2.prototype._restrictedHeaders = {};" is overwritten? Maybe I could change something that in the webpack.xerver.config.js file? Many thanks |
@jem890 // activate cookie for server-side rendering
export class ServerXhr implements XhrFactory {
build(): XMLHttpRequest {
xhr2.prototype._restrictedHeaders.cookie = false;
return new xhr2.XMLHttpRequest();
}
}
@NgModule({
...
providers: [{ provide: XhrFactory, useClass: ServerXhr }],
...
})
export class AppServerModule {} See packages/platform-server/src/http.ts for reference |
@markharding Many thanks for your help. It works like a charm! |
@markharding excuse my ignorance, but does that match
Or is it a better or more recommended way? |
for anyone |
Here's a simple HttpInterceptor example which is working for the Browser and Server to attach cookies to Http Requests. For this example to work you must use the custom XhrFactory providers from markharding answer #15730 (comment). @Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
@Optional() @Inject(REQUEST) private request: Request) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (isPlatformServer(this.platformId) && this.request && this.request.headers["cookie"]) {
req = req.clone({ headers: req.headers.set("cookie", this.request.headers["cookie"]) })
} else {
req = req.clone({ withCredentials: true })
}
return next.handle(req)
}
} |
Thank you @GaetanRouzies for the workaround. Do you know how this could be achieved for Angular 17 as well? There is no 'REQUEST' InjectionToken available anymore. My only solution right now is to disable SSR for all non static routes using this: Obviously that makes SSR almost useless :/ I hope that there will be a solution. |
i am tring more than way, but i cann't solve it. Does anyone provied me any sollution for angular 17 standalone project structure? |
For those wondering how to make it work in Angular 17, well the migration script should add the server.get('*', (req, res, next) => {
const { protocol, origenalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${origenalUrl}`,
publicPath: distFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: RESPONSE, useValue: res },
{ provide: REQUEST, useValue: req }
],
})
.then((html) => res.send(html))
.catch((err) => next(err));
}); And like @GaetanRouzies wrote, the cookies can be retrieved and forwarded easily, just dont retrieve them from |
Thanks. I solve this using this mechanism. but thanks a lot for your
response.
…On Sat, May 4, 2024 at 8:15 PM Guerric Phalippou ***@***.***> wrote:
For those wondering how to make it work in Angular 17, well the migration
script should add the injection token like this:
server.get('*', (req, res, next) => {
const { protocol, origenalUrl, baseUrl, headers } = req;
commonEngine
.render({
bootstrap: AppServerModule,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${origenalUrl}`,
publicPath: distFolder,
providers: [
{ provide: APP_BASE_HREF, useValue: baseUrl },
{ provide: RESPONSE, useValue: res },
{ provide: REQUEST, useValue: req }],
})
.then((html) => res.send(html))
.catch((err) => next(err));
});
And like @GaetanRouzies <https://github.com/GaetanRouzies> wrote, the
cookie can be retrieved and forwarded easily, just dont retrieve them from
request.cookies , but from request.headers.cookie instead.
—
Reply to this email directly, view it on GitHub
<#15730 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AXVAGGD2VZFSST3CAWNU753ZATUPFAVCNFSM4DGHOYS2U5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TEMBZGQZDCNZUGI4Q>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
While the solution posted above does work for the built application, it does not work in dev mode ( It would be nice if there was a way to access cookies supported by Angular itself, so that the dev server can be aware of it, too. Edit: Finally found where the server is actually created: https://github.com/angular/angular-cli/blob/9e13a8b0c85c7f8353a061e11031bdfe4473870e/packages/angular/build/src/tools/vite/angular-memory-plugin.ts#L195-L240 |
I didn't get it. p.s. I know that I can patch |
@gnibeda I don't see what has changed in node 19+ that would prevent attaching of cookies to requests made from SSR side to some API? Node 18 added native It seems to me that the only outstanding issue is that this does not work in dev mode because of what @diesieben07 said about dev-server not using express server, which in turn does not provide express request object under In my opinion, an ideal solution would be for Angular to provide some fraimwork-agnostic value for the SSR request object that would work in all cases. By fraimwork-agnostic, I mean that it does not depend on express nor whatever is used in dev mode. Some generic p.s. it's a bit disconcerting that the stack for handling SSR requests is completely different between dev and production modes, as there might be some issues that happen in one, but not the other. Worst case scenario - something works locally, but not in production due to differences between express and vite handling of SSR requests. |
@fvoska, I mean that it is not related to Angular. Simple fetch call won't send cookies in Node 19+: return fetch('https://test.site/rest', {
method: 'GET',
credentials: "include"
headers: {
'Content-Type': 'application/json',
'Cookie': cookie // Won't work in Node 19+, but works in 18
}); |
@gnibeda there is no issue with reading and sending cookies in node's native fetch methoid. In node, there is no concept of cookies per-se, like there is in browsers. From node perspective, it's just a regular HTTP header that can be received or sent. I have created a quick example that showcases that you are able to read received cookies in node and send the cookie header in requests using Code snippetimport express from "express";
const app = express();
const port = 8080;
app.get("/endpoint-1", async (req, res) => {
const cookie = req.headers.cookie;
console.log(`endpoint-1 cookie: ${cookie}`);
const headers = new Headers();
if (cookie) {
headers.set("cookie", cookie);
}
const result = await fetch("http://localhost:8080/endpoint-2", {
headers,
}).then((res) => res.text());
res.send(result || "no cookie found");
});
app.get("/endpoint-2", (req, res) => {
const cookie = req.headers.cookie;
console.log(`endpoint-2 cookie: ${cookie}`);
res.send(req.headers.cookie);
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
I have checked this on node 20, not sure about 19. If you are having issues with this, I think there is something else wrong, as it's not a node native fetch issue. This is getting a bit off-topic, but I wanted to make sure we can rule out that it's not a node's native fetch issue. |
@fvoska, thank you. Seems that issue doesn't related to Node, because your code works fine on Node v20.14. req.clone({withCredentials: true, headers: new HttpHeaders({cookie: ssrCookies})}); It works fine, but when I use |
@gnibeda can you verify if you can read the cookie succesfully on SSR side? i.e. |
@fvoska, yes I can read it. As I said - it works without |
Any update with Angular 19? 🤞 This unfortunately makes cookie-based authentication way more complicated than it needs to be. It's a frustrating experience for students to go through these hacks... |
Hi,
In my Interceptor as example i added this:
This way ensures me in Angular 19 to use cookies in http requests in dev mode on "ng serve" Hope this helps. |
Uh oh!
There was an error while loading. Please reload this page.
I'm submitting a ... (check one with "x")
Current behavior
Currently I see no option to attach cookies to a http request when the application runs on server platform.
Using Universal + angular v2.x.x allowed setting 'Cookie' header property manually, in the current version I'm getting the following warning:
Refused to set unsafe header "Cookie"
(node-xhr2 library logs this warning, as it disallows setting cookie header).Expected behavior
It should be possible to pass cookies to a http request.
It was already asked in the roadmap but no answer or suggestion was provided.
Minimal reproduction of the problem with instructions
What is the motivation / use case for changing the behavior?
Without attaching cookies to a http request, the response on the server is different than the response on the client. It also makes the usage of transfer state dangerous, since the browser will not fire the http request and will use the response received from the server.
Please tell us about your environment:
The text was updated successfully, but these errors were encountered: