Set cookie in Angular Universal during SSR

25 Nov 2017

Angular Universal allows you to build isomorphic apps, that will be pre-rendered on the server and again it boots from that rendered state to active in the client browser. This kind of application provides SEO benefits as well as perceived responsiveness for visitors.

Pre-rendering of unauthenticated pages works fine, as it doesn't depend upon any stored state/session. When it comes to pre-rendering authenticated pages, it depends upon the cookies or token of current logged-in user to layout the page structure. e.g., Your website homepage contains lot of content customized based on the user preference. List of information that he is interested to appear on homepage. You either have to completely forgo SSR and deal with loading AJAX data in a smoother way.

This article deals on how to handle those situation with SSR enabled.

When a logged-in user directly loads SSR enabled webpage or route on browser. The initial request from the browser reaches the server with the required Cookies in the request header. You can read the request headers by importing following universal classes in main.server.ts.

import { REQUEST } from '@nguniversal/aspnetcore-engine';
export class AppComponent implements OnInit, OnDestroy {
constructor(@Inject(REQUEST) private request){
    console.log('This contains your request headers : ' + request );
}
}

You can use the above request information in your XHR requests on the Server. When you try to do it i.e., to set the Cookies to all your XHR request, you will end up receiving following error

Refused to set unsafe header "cookie".

This will look as a huge blockade, and any Google search will return that it is an expected behaviour of the browsers and advice you not to do it that way. You'll tend to believe that SSR for authenticated page is impossible.

Wait! before you fall for that trap in Universal Rendering. Ask a simple query

Whether a browser component is used to pre-render an Angular page on server ?

Answer is NO, which opens up with a possibility to workaround this issue. Angular Universal uses pseudo XHR library(xhr2) to emulate the XHR requests from the app server to the service hosts. So when you try to setup the cookie in the headers, the error message that you see is thrown by xhr2 library. So you can instruct the xhr2 library to remove this restriction in your main.server.ts as follows.

import * as xhr2 from 'xhr2';
xhr2.prototype._restrictedHeaders = {};

Please use above way to set cookies or authentication token header during SSR, don't try to use this methods on browser where you will not have access to remove this restriction. Following shows a way on how to set the cookie header during XHR requests in Server.

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { ConnectionBackend, Http, Request, RequestOptions, RequestOptionsArgs, Response, Headers } from '@angular/http';
import { TransferState } from '../transfer-state/transfer-state';
import { isPlatformServer } from '@angular/common';
import { REQUEST } from '../../app/shared/constants/request';


@Injectable()
export class TransferHttp {

    private isServer = isPlatformServer(this.platformId);
    private strCookie = '';

  constructor(
    @Inject(PLATFORM_ID) private platformId,
    private http: Http,
    protected transferState: TransferState,
    @Inject(REQUEST) private req: Request
  ) { }

  /**
   * Performs a request with `get` http method.
   */
  get(url: string, options?: RequestOptionsArgs): Observable<any> {
    let options = this.setCookieInServer(options);
    return this.http.get(url, options);
  }
// ...

  private setCookieInServer(options: RequestOptionsArgs) {
      if (this.isServer) {
          if (!options) {
              options = {};
              options.headers = new Headers();
          }
          if (!this.strCookie && this.req) {
              this.req.headers['cookie'].forEach((c: string) => {
                  this.strCookie += c + ';';
              });

              if (this.strCookie.length) {
                  this.strCookie = this.strCookie.substr(0, this.strCookie.length - 1);
              }
          }
          options.headers.set('cookie', this.strCookie);
      }
      return options;
  }
}

After removing the restriction, you can set the cookie successfully on server.