Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Feature Request: Support server-side authentication in AngularFireAuth #1500

AnthonyNahas started this conversation in Ideas
Discussion options

I am currently developing an angular universal app powered by firebase and angularfire2 (for authentication and database/firestore).
For the server side rendering i am using NodeJS.

I have the following scenario:

There is many routes and many components inside the project! For the following scenario i will consider only 3 routes/components

1- Home Component (authentication is not required)
2- UserProfile Component (Authentication is required)
3- Login Component (used as fallback route to authenticate the user that are trying to access the user profile without to be logged in)!

To make things working with an authentication process within an angular app, we use the "canActivate guard"!

Workflow in the canActivate guard:

  • entry point: HomeComponent
  • if the user is authenticated, the user will be redirected to the UserProfile component
  • otherwise, he will be redirected to the login component.
@Injectable()
export class AuthGuard implements CanActivate {
 constructor(private auth: AuthService, private router: Router) {
 }
 canActivate(next: ActivatedRouteSnapshot,
 state: RouterStateSnapshot): Observable<boolean> | boolean {
 return this.auth.user$
 .take(1)
 .map(user => !!user)
 .do(loggedIn => {
 if (!loggedIn) {
 console.log('access denied');
 this.router.navigate(['/login']);
 }else{
 this.router.navigate(['/user']); 
 } 
 });
 }
}

this.auth.user$ is equal to

@Injectable()
export class AuthService {
 public user: IUser;
 public user$: Observable<IUser>;
 private _authDialogRef: MatDialogRef<any>;
 constructor(private afAuth: AngularFireAuth,
 private afs: AngularFirestore,
 private _dialog: MatDialog,
 private router: Router) {
 // Get auth data, then get firestore user$ document || null
 this.user$ = this.afAuth.authState

However, client side rendering is working like expected but no the server side!
Since in nodejs the firebase authentication's credentials are not known, the user$ Observable is never fired on the server side (the credentials are located in the local storage of the browser under the key firebase:authUser......).

Angular Universal - SSR - canActivate Result:

when the guard is triggered on the server side, the login page will be rendered and then after few seconds the client side render the whole thing again and will be redirected to the user page.
Than means, by refreshing and rendering every time the user will see first the login page then the user page! This is happening one more time because on the server side are NEVER authenticated..

How can we solve the above described scenario?

Request

Please support firebase authentication on the server side too

Note (ideas): supporting firebase authentication for angular universal app through JWT - headers / cookies ????

You must be logged in to vote

Replies: 24 comments 8 replies

Comment options

I have the same problem, looking for solution of this scenario :) if we can fix this, it will be completed eco system for angular with minimum cost. what about angular state transfer ?

You must be logged in to vote
0 replies
Comment options

While not an official solution, I have used dependency injection + firebase admin to work on server side

See:

https://github.com/patrickmichalina/fusebox-angular-universal-starter/blob/master/src/server/server.angular.module.ts#L92

https://github.com/patrickmichalina/fusebox-angular-universal-starter/blob/master/src/client/app/shared/services/auth.service.ts

The idea is, you only need to use the AuthService and it will work on both the server and client via cookies. Not elegant, but it works!

You must be logged in to vote
0 replies
Comment options

  1. why did you use cookies strategy instead of headers ?
  2. Can you please provide a brief workflow to the ssr authentication (incl. how can the firebase team enhance the authentication module to support server side rendering...)
You must be logged in to vote
0 replies
Comment options

How do you intend to transmit a Authorization header from the first server request? The web browser WILL send stored cookies, but has no ability to know you intend to send an Authorization header. Cookies are also more secure in general. I highly recommend you use Cookies in your universal application.

You must be logged in to vote
0 replies
Comment options

@davideast do you have any suggestion sir?

You must be logged in to vote
0 replies
Comment options

Like @hiepxanh mentioned above... This would complete the SSR + Angular + Firebase visionquest.

The only thing I've come up w/ so far (and this just a hack to get rid of auth'd refresh flicker) is to return true; until you get to the client:

canActivate(): Observable<boolean> | boolean {
 if (typeof window !== 'undefined') {
 return this.afAuth.user.pipe(
 take(1),
 map(state => !!state),
 tap(loggedIn => {
 if (!loggedIn) {
 this.system.log('You must be logged in.');
 this.router.navigate(['/login']);
 }
 })
 );
 }
 return true;
 }

This just flips the problem around, to where when you hit a route-guarded page without auth, it will flicker the unauthorized page on the initial render, and then kick you back to the login page once it's got the firebase user response in the client. Again, FYI - This is a total hack.

You must be logged in to vote
1 reply
Comment options

this saved my day 7 years later

Comment options

Just checking back in here to see if anyone has made any progress on this?

You must be logged in to vote
0 replies
Comment options

I've looking into this BTW. For server side auth right now I recommend a cloud function that issues a cookie via the admin SDK. I'll come up with some recommendations / put some work into the Auth module soon to make it work better out of the box.

You must be logged in to vote
0 replies
Comment options

I will also be writing a module for this soon - will post back here once its ready :)

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
0 replies
Comment options

Hey @jrodl3r ,

did you try this new things out? Is it working with ssr?

Thanks

You must be logged in to vote
0 replies
Comment options

hi @simkepal, currently setting up a system w/ latest angular-universal (8) on a gcloud app-engine instance - so far, SSR is working great with node 10.

will update after setting up auth and testing out new guard stuff 👍

You must be logged in to vote
0 replies
Comment options

@jamesdaniels so, this works on local angular + SSR from gCloud instance:

const isAdmin = () => pipe(customClaims, map(claims => {
 return claims.admin === true ? claims.admin : ['/'];
}));
const routes: Routes = [
 ...,
 {
 path: 'admin',
 component: AdminComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 },
];

however, the pre-built ones like hasCustomClaim('admin') or redirectUnauthorizedTo([‘login’]) only hold-up locally and break on SSR:

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['login']);
const routes: Routes = [
 ...,
 { path: 'profile', component: ProfileComponent,
 ...canActivate(redirectUnauthorizedToLogin) },
];

this has been crashing my canary tab regularly =\

const redirectUnauthorized = map((user: IUser) =>
 user ? ['profile'] : ['login']);

seeing this pop up a bunch:
Screen Shot 2019年07月05日 at 12 08 05 AM

great news that we can verify the admin claim, just need to fix verifying the logged-in user.


Update: this works in both local Angular + gCloud SSR instance 😄

const isLoggedIn = () => pipe(map(user => !!user ? true : ['/']));
const isLoggedOut = () => pipe(map(user => !!user ? ['profile'] : true));
const isAdmin = () => pipe(customClaims, map(claims =>
 claims.admin === true ? claims.admin : ['/']
));
const routes: Routes = [
 { path: '', component: HomeComponent },
 {
 path: 'login',
 component: LoginComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isLoggedOut }
 },
 {
 path: 'admin',
 component: AdminComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 },
 {
 path: 'profile',
 component: ProfileComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isLoggedIn }
 },
 { path: '**', component: ErrorComponent }
];

also works on lazy-loaded module routes 👍
follow this guide to avoid pesky server-side errors: https://github.com/angular/universal/blob/master/docs/v8-upgrade-guide.md

const routes: Routes = [
 ...,
 {
 path: 'admin',
 loadChildren : () => import('./components/_admin/admin.module').then(m => m.AdminModule),
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 }
}
You must be logged in to vote
0 replies
Comment options

I am so confused, could someone wrap it up? does AngularFireAuth authState observable return anything on server side? do you have plans for it? Notice, a guard is not the issue, its using the firebase token in an API call to a different server... HELP!

You must be logged in to vote
0 replies
Comment options

I found a good stopgap solution, until there's a good way to check auth server-side with Angular Universal.

Assuming your guard redirects to login, on your login page, show a loading spinner until the frontend can resolve auth. Then what happens is, if you try to load an auth-protected route, the user sees a loading spinner momentarily until auth resolves.

To make this work, you have to set up your login template to ensure it prerenders showing the spinner. This is ok, since you should not need SEO on your login route. I did it like this:

login.component.html:

<ng-container *ngIf="authResolved; else showLoading">
 <!-- login form here -->
</ng-container>
<ng-template #showLoading>
 <!-- this is a component that holds a spinner -->
 <app-loading-overlay [loading]="true" behavior="full"></app-loading-overlay>
</ng-template>

login.component.ts:

authResolved = false;
constructor(
 private afAuth: AngularFireAuth,
 @Inject(PLATFORM_ID) private platformId,
) {}
ngOnInit() {
 if (isPlatformBrowser(this.platformId)) {
 // show spinner till frontend can determine if user is logged in
 this.afAuth.authState.pipe(
 map(user => !!user),
 take(1) // this way the observable completes and we don't need to unsubscribe
 ).subscribe(() => {
 this.authResolved = true;
 });
 }
 }
You must be logged in to vote
0 replies
Comment options

@jamesdaniels so, this works on local angular + SSR from gCloud instance:

const isAdmin = () => pipe(customClaims, map(claims => {
 return claims.admin === true ? claims.admin : ['/'];
}));
const routes: Routes = [
 ...,
 {
 path: 'admin',
 component: AdminComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 },
];

however, the pre-built ones like hasCustomClaim('admin') or redirectUnauthorizedTo([‘login’]) only hold-up locally and break on SSR:

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['login']);
const routes: Routes = [
 ...,
 { path: 'profile', component: ProfileComponent,
 ...canActivate(redirectUnauthorizedToLogin) },
];

this has been crashing my canary tab regularly =\

const redirectUnauthorized = map((user: IUser) =>
 user ? ['profile'] : ['login']);

seeing this pop up a bunch:
Screen Shot 2019年07月05日 at 12 08 05 AM

great news that we can verify the admin claim, just need to fix verifying the logged-in user.

Update: this works in both local Angular + gCloud SSR instance 😄

const isLoggedIn = () => pipe(map(user => !!user ? true : ['/']));
const isLoggedOut = () => pipe(map(user => !!user ? ['profile'] : true));
const isAdmin = () => pipe(customClaims, map(claims =>
 claims.admin === true ? claims.admin : ['/']
));
const routes: Routes = [
 { path: '', component: HomeComponent },
 {
 path: 'login',
 component: LoginComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isLoggedOut }
 },
 {
 path: 'admin',
 component: AdminComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 },
 {
 path: 'profile',
 component: ProfileComponent,
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isLoggedIn }
 },
 { path: '**', component: ErrorComponent }
];

also works on lazy-loaded module routes 👍
follow this guide to avoid pesky server-side errors: https://github.com/angular/universal/blob/master/docs/v8-upgrade-guide.md

const routes: Routes = [
 ...,
 {
 path: 'admin',
 loadChildren : () => import('./components/_admin/admin.module').then(m => m.AdminModule),
 canActivate: [AngularFireAuthGuard],
 data: { authGuardPipe: isAdmin }
 }
}

Thanks that helped me a lot

You must be logged in to vote
0 replies
Comment options

Are there any plans to make the AngularFireAuth Service work on server for SSR?
Using the AngularFireAuthGuard is not always an option.

You must be logged in to vote
0 replies
Comment options

I'm also getting stuck with the same issue in Angular SSR. Is there any solution for it?
In my project, I'm using express as a backend JWT token for authentication and Angular SSR for as my app.

You must be logged in to vote
0 replies
Comment options

I'm hoping to add official support for it in a future release but I'd suggest cookie auth for SSR.

You must be logged in to vote
0 replies
Comment options

Is there any progress on SSR with authentification? An official solution would be much appreciated. I'm using AngularFire with email + password authentification and would like to add SSR if there is an easy solution. I read that we can use SSR without authentification out of the box and can even "activate" a cloud function. But I'm unsure what the cloud function does. SSR is actually new for me so I would very much appreciate any advise on how to implement SSR with authentificiation. Probably this is already working out of the box and I just missed something, did I?

Love AngularFire btw. Great work.

You must be logged in to vote
0 replies
Comment options

Hi @jamesdaniels, are there still any plans for this feature request? Thank you in advance.

You must be logged in to vote
3 replies
Comment options

We're actively working on a proper server-side auth story for Firebase ATM. No ETA to share but it's a high priority.

Comment options

We're actively working on a proper server-side auth story for Firebase ATM. No ETA to share but it's a high priority.

Cool, thank you for the update.

Do you have an example repo with a workaround that can be used in meantime?

That would help a LOT.

Thank you in advance.

Comment options

There's not currently a work-around that I can give and feel good about; even the best solutions would be prone to race conditions and require significant gymnastics to keep the client and server state in sync. While this has been open since 2018, we're finally getting some attention on it internally & I expect quick progress to be made—relatively speaking.

Comment options

@jamesdaniels It's been a year since the last update. Any chance official SSR + Auth will be looked at soon?

You must be logged in to vote
0 replies
Comment options

You must be logged in to vote
2 replies
Comment options

This Angular team is hoping to allow this solution to work in dev-mode sometime after v18 is released

Comment options

This doesn't work in Firefox. Even in Firebase documentation site, icons are not loading in Firefox

Comment options

Greetings, I am a user migrating to angular and I just found this issue for a dashboard app that we are building

I just found that flicker behavior for auth routes and ended in this thread discussion

how this use case is being handled nowadays?

Is it’s possible to disable SSR with angular 19 for protected routes in the meantime ?

You must be logged in to vote
2 replies
Comment options

Yes, quite easily in fact. Just ask Copilot how to do it and it’ll walk you through how to setup the route. You pretty much just declare ssr: false in the route object. GPT 4.1 model does a very good job of explaining how things like this work.

Comment options

Hi @MarshallBelles excuse me, but one of the reasons I am also researching by my own is because LLMs like Claude and gpt 4.1 tried approaches that didn’t deliver the expected behavior

the code generated by LLMs for the guards is the same, inject the auth service in the guard, take the user and map the result within the redirection

But this approach is broken when the request is handled by the server, the user is always null even if the user was previously authenticated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /