-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Feature Request: Support server-side authentication in AngularFireAuth #1500
-
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 ????
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 11 -
😕 1 -
❤️ 1
Replies: 24 comments 8 replies
-
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 ?
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
While not an official solution, I have used dependency injection + firebase admin to work on server side
See:
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!
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 3
-
- why did you use cookies strategy instead of headers ?
- 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...)
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@davideast do you have any suggestion sir?
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
this saved my day 7 years later
Beta Was this translation helpful? Give feedback.
All reactions
-
Just checking back in here to see if anyone has made any progress on this?
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
I will also be writing a module for this soon - will post back here once its ready :)
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
just saw these... 🤔
Beta Was this translation helpful? Give feedback.
All reactions
-
Beta Was this translation helpful? Give feedback.
All reactions
-
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 👍
Beta Was this translation helpful? Give feedback.
All reactions
-
@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 } } }
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 3
-
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!
Beta Was this translation helpful? Give feedback.
All reactions
-
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; }); } }
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@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')
orredirectUnauthorizedTo([‘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 AMgreat 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.mdconst 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
Beta Was this translation helpful? Give feedback.
All reactions
-
Are there any plans to make the AngularFireAuth Service work on server for SSR?
Using the AngularFireAuthGuard is not always an option.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
I'm hoping to add official support for it in a future release but I'd suggest cookie auth for SSR.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👀 1
-
Hi @jamesdaniels, are there still any plans for this feature request? Thank you in advance.
Beta Was this translation helpful? Give feedback.
All reactions
-
We're actively working on a proper server-side auth story for Firebase ATM. No ETA to share but it's a high priority.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1 -
😕 1
-
@jamesdaniels It's been a year since the last update. Any chance official SSR + Auth will be looked at soon?
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 8
-
We now have FirebaseServerApp, you can combine this with Service Workers to pass an authorization header! https://firebase.google.com/docs/auth/web/service-worker-sessions#web-namespaced-api_5
Added to the documentation here https://github.com/angular/angularfire/blob/master/docs/auth.md#server-side-rendering
Beta Was this translation helpful? Give feedback.
All reactions
-
This Angular team is hoping to allow this solution to work in dev-mode sometime after v18 is released
Beta Was this translation helpful? Give feedback.
All reactions
-
This doesn't work in Firefox. Even in Firebase documentation site, icons are not loading in Firefox
Beta Was this translation helpful? Give feedback.
All reactions
-
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 ?
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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
Beta Was this translation helpful? Give feedback.