The AngularFire auth guard doc shows different ways to allow authentication.
Only admin users:
const editorOnly = pipe(customClaims, map(claims => claims.role === "editor"));
Self only:
const onlyAllowSelf = (next) => map(user => !!user && next.params.userId === user.uid);
My question, is how to combine the two that either an editor/admin or the user himself can open the component.
You can create a combineAuthPipes
function, which uses forkJoin
under the covers:
const combineAuthPipes = (authPipes: AuthPipe[]) =>
switchMap((t: Observable<firebase.User>) => forkJoin(authPipes.map(x => x(t))))
const loggedInThenRedirect = pipe(
map((t: firebase.User) => of(t)),
combineAuthPipes([
loggedIn,
customClaims as AuthPipe,
hasCustomClaim('admin'),
hasCustomClaim('editor')
]),
map(([isLoggedIn, customClaimList, admin, moderator]) => {
return {
loggedIn: isLoggedIn,
customClaims: customClaimList,
admin,
moderator
}
}),
map((t) => {
if (t.admin) {
return true
}
if (!t.loggedIn) {
return ['login']
}
return ['unauthorized']
})
)
Use like this:
export const routes: Routes = [
{
path: '', component: SomeComponent,
pathMatch: 'full',
canActivate: [
AngularFireAuthGuard
],
data: { authGuardPipe: () => loggedInThenRedirect }
}
]
As long as your final function adheres to the AuthPipe
interface (mapping Observable<firebase.User | null>
to Observable<boolean | string | string[]>
) you can compose any custom pipe.
The solution I went for used:
pipe
operator to pipe into the final AuthPipe
interface.forkJoin
to join together the results from multiple AngularFireAuth
pipes.map
to convert the intermediary results to boolean | string | string[]
.With this approach you can use the results of multiple AuthPipe
s when implementing canActivate
logic.
(essentially a cleaner version of @pixelbits answer)
import { AngularFireAuthGuard, AuthPipe, emailVerified, loggedIn } from '@angular/fire/compat/auth-guard';
import { forkJoin, of, pipe } from 'rxjs';
const combined: AuthPipe = pipe(
mergeMap((user) => forkJoin([loggedIn(of(user)), emailVerified(of(user))])),
map(([isLoggedIn, isEmailVerified]) => {
console.log({ isLoggedIn, isEmailVerified });
if (!isLoggedIn) {
return '/auth/login';
}
if (!isEmailVerified) {
return '/auth/verify-email';
}
return true;
}),
);
// usage
{
component: MyComponent,
canActivate: [AngularFireAuthGuard],
data: { authGuardPipe: () => combined },
},
A note on the customClaims
AuthPipe
: it expects firebase.User
(does not allow for null
), so you can either:
user ? customClaims(of(user)) : of(null)
orcustomClaims(of(user as any))
, which returns an empty array when user is null (source)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With