#Angular 17 SSR is not sending Authentication Token

16 messages · Page 1 of 1 (latest)

strong current
#

Hi devs! I need some help regarding SSR in Angular 17.
Previously without SSR the auth interceptor appends the bearer token taken from LocalStorage, now with SSR in Angular 17 looks like something is wrong and it is not working at all, API says that it is unable to retreive user ID, that's because the interceptor is not sending anything.

export class AuthInterceptorService implements HttpInterceptor {
  constructor(
    private readonly localService: LocalStorageService,
    @Inject(PLATFORM_ID) private platformId: Object
  ) {}

  intercept(
    httpRequest: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (isPlatformBrowser(this.platformId)) {
      const token = this.localService.getToken();
      if (token !== null) {
        httpRequest = httpRequest.clone({
          setHeaders: {
            Authorization: `Bearer ${token}`,
          },
        });
      }
    }

    return next.handle(httpRequest);
  }
}

And my server.ts looks like this

server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers } = req;
    commonEngine
      .render({
        bootstrap: AppServerModule,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => res.send(html))
      .catch((err) => next(err));
  });

  return server;
}

I have no idea how to handle this, can somebody help me out?

strong current
#

What is Dm?

#

I believe this is not precisely a ticket which should be created, it is more a solution regarding the code itself, something is possibly missing in the server.ts or some other step should be done when using SSR in Angular 17

brisk steppe
#

Hi @strong current , here are my thoughts.

#
  1. Set the Token in a Cookie (Client-Side)
    Modify your authentication service or method that retrieves the token to set it as a cookie.

setTokenInCookie(token: string): void {
document.cookie = authToken=${token}; path=/;;
}

2.Modify the Interceptor.
Update the interceptor to read the token from cookies instead of localStorage. You can use a third-party package like cookie to help with cookie management.

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { isPlatformBrowser } from '@angular/common';
import { Observable } from 'rxjs';
import * as cookie from 'cookie'; // Import the cookie package

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}

intercept(
httpRequest: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let token = null;

if (isPlatformBrowser(this.platformId)) {
  const cookies = cookie.parse(document.cookie || '');
  token = cookies.authToken;
} else {
  token = someGlobalOrInjectedServerValue.authToken;
}

if (token) {
  httpRequest = httpRequest.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });
}

return next.handle(httpRequest);

}
}

#

3.Pass the Token from Client to Server.
In your server.ts, ensure that cookies are properly handled and passed to the server-side code. For instance, you could set up middleware that parses cookies on every request and passes them as an injectable dependency.

const express = require('express');
const cookieParser = require('cookie-parser');

const server = express();
server.use(cookieParser());

server.get('*', (req, res, next) => {
const token = req.cookies.authToken;
commonEngine
.render({
// Existing properties...
})
.then((html) => res.send(html))
.catch((err) => next(err));
});

return server;

#

Please let me know what's your thoughts.

strong current
#

I am confused in something here

server.get('*', (req, res, next) => {
  const token = req.cookies.authToken; 
  commonEngine
    .render({
      // Existing properties...
    })
    .then((html) => res.send(html))
    .catch((err) => next(err));
});

How I send the cookie?? Where to add it?

strong current
#

@brisk steppe ok it looks like that it's working, but once I reload the page, the cookie dissapear totally from the Cookies.

brisk steppe
#

Hmm.. Will be. If the cookie is created without an expiration date, it will only last for the session and be removed once the browser is closed.

#

To make it persistent, you can set an expiration date in the document.cookie setting..

#

function setTokenInCookie(token) {
const expires = new Date();
expires.setTime(expires.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days
document.cookie = authToken=${token}; path=/; expires=${expires.toUTCString()}; Secure; SameSite=Lax;
}

If this code is working, please let me know.

strong current
#

Unfortunately no, same problem,

  1. The SSR works fine first time, if I reload the page, cookies disappeared, and the cookies are no longer accessible.

  2. If the page is reloaded, then the APIs cannot validate that the user is in session (authenticated), the API is .net core, the token is never sent.

I am so surprised that Angular is good for many things but for SSR not! Even the documentation in the official site is terrible.

I have to clue! 😦

#

Server.ts

// All regular routes use the Angular engine
  server.get('*', (req, res, next) => {
    const { protocol, originalUrl, baseUrl, headers, cookies } = req;
    const token = cookies.authToken;
    if (token) {
      headers.authorization = `Bearer ${token}`;
    }
    commonEngine
      .render({
        bootstrap: AppServerModule,
        documentFilePath: indexHtml,
        url: `${protocol}://${headers.host}${originalUrl}`,
        publicPath: browserDistFolder,
        providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
      })
      .then((html) => {
        res.send(html);
      })
      .catch((err) => next(err));
  });

  return server;

app,module.server.ts

@NgModule({
  imports: [AppModule, ServerModule],
  bootstrap: [AppComponent],
  providers: [
    CookieService,
    provideHttpClient(withFetch()),
    provideClientHydration(),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthServerInterceptorService,
      multi: true,
    },
  ],
})
export class AppServerModule {}

app.module.ts

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ToastrModule.forRoot(),
    StoreModule.forRoot(),
    EffectsModule.forRoot(),
    BrowserAnimationsModule
  ],
  providers: [
    CookieService,
    provideClientHydration(),
    provideAnimations(), // required animations providers
    provideToastr(),
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthServerInterceptorService,
      multi: true,
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
#

localStorageService.service.ts

setLoggedUser(user: string, token: string): void {
    this.setItem(this.user, JSON.stringify(user));
    this.setItem(this.token, token);
    this.cookieService.set('authToken', token, {
      expires: 7,
      domain: 'localhost'
    });
    this.cookieService.set(this.user, JSON.stringify(user), {
      expires: 7,
      domain: 'localhost'
    });
  }

I don't really know how exactly this should be done!

ornate pawn
#

Did you ever got around this?