How to implement role-based auth in Angular

Role-based authorization controls access to application features, routes, and UI elements based on user roles, ensuring proper security and user experience. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented RBAC systems in enterprise Angular applications throughout my 12 years of frontend development since 2014. The most comprehensive approach combines route guards for navigation protection, services for permission checking, and directives for conditional UI rendering. This method provides layered security, prevents unauthorized access, and maintains clean separation between authorization logic and components.

Create auth service with role management, implement route guards checking user roles, build directive for role-based element visibility.

// src/app/core/services/auth.service.ts
import { Injectable } from '@angular/core'
import { BehaviorSubject, Observable } from 'rxjs'
import { map } from 'rxjs/operators'

export interface User {
  id: string
  username: string
  email: string
  roles: string[]
  permissions: string[]
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private currentUserSubject: BehaviorSubject<User | null>
  public currentUser$: Observable<User | null>

  constructor() {
    const storedUser = localStorage.getItem('currentUser')
    this.currentUserSubject = new BehaviorSubject<User | null>(
      storedUser ? JSON.parse(storedUser) : null
    )
    this.currentUser$ = this.currentUserSubject.asObservable()
  }

  get currentUserValue(): User | null {
    return this.currentUserSubject.value
  }

  login(username: string, password: string): Observable<User> {
    return new Observable(observer => {
      setTimeout(() => {
        const user: User = {
          id: '1',
          username,
          email: `${username}@example.com`,
          roles: ['admin', 'user'],
          permissions: ['read', 'write', 'delete']
        }
        localStorage.setItem('currentUser', JSON.stringify(user))
        this.currentUserSubject.next(user)
        observer.next(user)
        observer.complete()
      }, 1000)
    })
  }

  logout(): void {
    localStorage.removeItem('currentUser')
    this.currentUserSubject.next(null)
  }

  hasRole(role: string): boolean {
    const user = this.currentUserValue
    return user ? user.roles.includes(role) : false
  }

  hasAnyRole(roles: string[]): boolean {
    const user = this.currentUserValue
    if (!user) return false
    return roles.some(role => user.roles.includes(role))
  }

  hasAllRoles(roles: string[]): boolean {
    const user = this.currentUserValue
    if (!user) return false
    return roles.every(role => user.roles.includes(role))
  }

  hasPermission(permission: string): boolean {
    const user = this.currentUserValue
    return user ? user.permissions.includes(permission) : false
  }

  hasAnyPermission(permissions: string[]): boolean {
    const user = this.currentUserValue
    if (!user) return false
    return permissions.some(permission => user.permissions.includes(permission))
  }
}
// src/app/core/guards/role.guard.ts
import { Injectable } from '@angular/core'
import { Router, ActivatedRouteSnapshot } from '@angular/router'
import { AuthService } from '../services/auth.service'

@Injectable({
  providedIn: 'root'
})
export class RoleGuard {
  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  canActivate(route: ActivatedRouteSnapshot): boolean {
    const user = this.authService.currentUserValue

    if (!user) {
      this.router.navigate(['/login'])
      return false
    }

    const requiredRoles = route.data['roles'] as string[]
    const requireAll = route.data['requireAll'] as boolean

    if (!requiredRoles || requiredRoles.length === 0) {
      return true
    }

    const hasAccess = requireAll
      ? this.authService.hasAllRoles(requiredRoles)
      : this.authService.hasAnyRole(requiredRoles)

    if (!hasAccess) {
      this.router.navigate(['/unauthorized'])
      return false
    }

    return true
  }
}
// src/app/core/directives/has-role.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef, OnInit, OnDestroy } from '@angular/core'
import { Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators'
import { AuthService } from '../services/auth.service'

@Directive({
  selector: '[appHasRole]',
  standalone: true
})
export class HasRoleDirective implements OnInit, OnDestroy {
  private roles: string[] = []
  private requireAll = false
  private destroy$ = new Subject<void>()

  @Input() set appHasRole(roles: string | string[]) {
    this.roles = Array.isArray(roles) ? roles : [roles]
    this.updateView()
  }

  @Input() set appHasRoleRequireAll(value: boolean) {
    this.requireAll = value
    this.updateView()
  }

  constructor(
    private templateRef: TemplateRef<any>,
    private viewContainer: ViewContainerRef,
    private authService: AuthService
  ) {}

  ngOnInit(): void {
    this.authService.currentUser$
      .pipe(takeUntil(this.destroy$))
      .subscribe(() => {
        this.updateView()
      })
  }

  ngOnDestroy(): void {
    this.destroy$.next()
    this.destroy$.complete()
  }

  private updateView(): void {
    const hasAccess = this.requireAll
      ? this.authService.hasAllRoles(this.roles)
      : this.authService.hasAnyRole(this.roles)

    if (hasAccess) {
      this.viewContainer.createEmbeddedView(this.templateRef)
    } else {
      this.viewContainer.clear()
    }
  }
}
// src/app/app.routes.ts
import { Routes } from '@angular/router'
import { RoleGuard } from './core/guards/role.guard'

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
    canActivate: [RoleGuard],
    data: { roles: ['user', 'admin'] }
  },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent),
    canActivate: [RoleGuard],
    data: { roles: ['admin'], requireAll: true }
  },
  {
    path: 'settings',
    loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent),
    canActivate: [RoleGuard],
    data: { roles: ['admin', 'moderator'] }
  },
  {
    path: 'login',
    loadComponent: () => import('./auth/login.component').then(m => m.LoginComponent)
  },
  {
    path: 'unauthorized',
    loadComponent: () => import('./error/unauthorized.component').then(m => m.UnauthorizedComponent)
  }
]
// src/app/components/example.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { HasRoleDirective } from '../core/directives/has-role.directive'
import { AuthService } from '../core/services/auth.service'

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [CommonModule, HasRoleDirective],
  template: `
    <div>
      <h2>Role-Based Content</h2>

      <div *appHasRole="'admin'">
        <p>This content is only visible to admins</p>
        <button>Admin Action</button>
      </div>

      <div *appHasRole="['admin', 'moderator']">
        <p>This is visible to admins or moderators</p>
      </div>

      <div *appHasRole="['admin', 'user']" [appHasRoleRequireAll]="true">
        <p>This requires both admin and user roles</p>
      </div>

      <button *appHasRole="'admin'" (click)="deleteUser()">
        Delete User
      </button>
    </div>
  `
})
export class ExampleComponent {
  constructor(private authService: AuthService) {}

  deleteUser(): void {
    if (this.authService.hasPermission('delete')) {
      console.log('Deleting user...')
    }
  }
}

Here the AuthService manages user authentication state and provides role checking methods. The BehaviorSubject enables reactive updates when user roles change. The RoleGuard implements canActivate interface to protect routes based on required roles. The route data property specifies required roles and whether all or any role is needed. The HasRoleDirective conditionally renders template based on user roles. The directive subscribes to currentUser$ for automatic updates when authentication state changes. The viewContainer.clear removes unauthorized content from DOM. Multiple role checking methods (hasRole, hasAnyRole, hasAllRoles) provide flexible authorization logic.

Best Practice Note:

This is the role-based authorization pattern we use in CoreUI Angular admin templates for enterprise-level access control. Never rely solely on frontend authorization as it can be bypassed, always implement matching backend authorization checks, store roles and permissions in JWT tokens or secure session storage, and implement audit logging for authorization failures to detect potential security breaches.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team