After a user is authenticated, if user is of admin role, the menu items - movie, genres, actor and movie theater are shown in the menu else they remained hidden. After my WEBAPI returns role as "admin", the menu items remain hidden. I am not able to figure out the issue.
app-routing.module.ts
import { RegisterComponent } from './security/register/register.component';
import { UserIndexComponent } from './security/user-index/user-index.component';
import { MovieDetailsComponent } from './movies/movie-details/movie-details.component';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CreateActorComponent } from './actors/create-actor/create-actor.component';
import { EditActorComponent } from './actors/edit-actor/edit-actor.component';
import { IndexActorsComponent } from './actors/index-actors/index-actors.component';
import { CreateGenreComponent } from './genres/create-genre/create-genre.component';
import { EditGenreComponent } from './genres/edit-genre/edit-genre.component';
import { IndexGenresComponent } from './genres/index-genres/index-genres.component';
import { HomeComponent } from './home/home.component';
import { CreateMovieTheaterComponent } from './movie-theaters/create-movie-theater/create-movie-theater.component';
import { EditMovieTheaterComponent } from './movie-theaters/edit-movie-theater/edit-movie-theater.component';
import { IndexMovieTheaterComponent } from './movie-theaters/index-movie-theater/index-movie-theater.component';
import { CreateMovieComponent } from './movies/create-movie/create-movie.component';
import { EditMovieComponent } from './movies/edit-movie/edit-movie.component';
import { MovieFilterComponent } from './movies/movie-filter/movie-filter.component';
import { IsAdminGuard } from './is-admin.guard';
import { LoginComponent } from './security/login/login.component';
const routes: Routes = [
{path:'', component:HomeComponent},
{path:'genres', component:IndexGenresComponent, canActivate:[IsAdminGuard]},
{path:'genres/create', component:CreateGenreComponent, canActivate:[IsAdminGuard]},
{path:'genres/edit/:id', component:EditGenreComponent, canActivate:[IsAdminGuard]},
{path:'actors', component:IndexActorsComponent, canActivate:[IsAdminGuard]},
{path:'actors/create', component:CreateActorComponent, canActivate:[IsAdminGuard]},
{path:'actors/edit/:id', component:EditActorComponent, canActivate:[IsAdminGuard]},
{path:'movietheaters', component:IndexMovieTheaterComponent, canActivate:[IsAdminGuard]},
{path:'movietheaters/create', component:CreateMovieTheaterComponent, canActivate:[IsAdminGuard]},
{path:'movietheaters/edit/:id', component:EditMovieTheaterComponent, canActivate:[IsAdminGuard]},
{path:'movies/create', component:CreateMovieComponent, canActivate:[IsAdminGuard]},
{path:'movies/edit/:id', component:EditMovieComponent, canActivate:[IsAdminGuard]},
{path:'movies/filter', component:MovieFilterComponent},
{path: 'movies/id', component:MovieDetailsComponent},
{path: 'register', component:RegisterComponent},
{path: 'login', component:LoginComponent},
{path:'users',component:UserIndexComponent,canActivate:[IsAdminGuard]},
// {path:'**',component:HomeComponent}
{path:'**',redirectTo:' '}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// @NgModule({
// imports: [RouterModule.forRoot(routes
// // ,
// // { enableTracing: true } // <-- debugging purposes only
// )],
// exports: [RouterModule]
// })
is-admin.guard.ts
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { SecurityService } from './security/security.service';
@Injectable({
providedIn: 'root'
})
export class IsAdminGuard implements CanActivate {
/**
*
*/
constructor(private securityService:SecurityService,private router:Router) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if(this.securityService.getRole()==='admin'){
return true;
}
this.router.navigate(['/login']);
return false;
}
}
menu.component.html
<mat-toolbar color="primary">
<span>
<a routerLink=" " mat-button>
<mat-icon>local_movies</mat-icon>
Angular Movies
</a>
</span>
<div>
<a mat-button routerLink="movies/filter">
<mat-icon>search</mat-icon>Search Movies
</a>
</div>
<app-authorize-view [role]="'admin'">
<ng-container authorized>
<div>
<a mat-button routerLink="genres">
Genres
</a>
</div>
<div>
<a mat-button routerLink="actors">
Actors
</a>
</div>
<div>
<a mat-button routerLink="movietheaters">
Movie Theaters
</a>
</div>
<div>
<a mat-button routerLink="movies/create">
Create Movie
</a>
</div>
<div>
<a mat-button routerLink="users">
Users
</a>
</div>
</ng-container>
</app-authorize-view>
<div class="space"></div>
<app-authorize-view>
<ng-container authorized>
<span>Hello,{{securityService.getFieldFromJWT('email')}}</span>
<a mat-button (click)="securityService.logout()">Logout</a>
</ng-container>
<ng-container notAuthorized>
<a mat-button routerLink="login">Login</a>
<a mat-button routerLink="register">Register</a>
</ng-container>
</app-authorize-view>
</mat-toolbar>
menu.component.ts
import { SecurityService } from './../security/security.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.css']
})
export class MenuComponent implements OnInit {
constructor(public securityService:SecurityService) { }
ngOnInit(): void {
}
}
app.component.html
<app-menu>
</app-menu>
<div class="container">
<router-outlet></router-outlet>
</div>
app.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SecurityService } from './security/security.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title(title: any) {
}
constructor(
private securityService: SecurityService,
private router: Router,
private activatedRoute: ActivatedRoute
) {}
ngOnInit(): void {
}
shouldRedirectToUser(): boolean{
if (this.securityService.isAuthenticated()){
return true;
}
console.log(this.router.url);
console.log(this.activatedRoute.url);
return false;
}
}
authorize-view.component.html
<ng-content *ngIf="!isAuthorized()" select=[notAuthorized]></ng-content>
<ng-content *ngIf="isAuthorized()" select=[authorized]></ng-content>
authorize-view.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { SecurityService } from '../security.service';
@Component({
selector: 'app-authorize-view',
templateUrl: './authorize-view.component.html',
styleUrls: ['./authorize-view.component.css'],
})
export class AuthorizeViewComponent implements OnInit {
constructor(private securityService: SecurityService) {}
@Input()
role: string;
ngOnInit(): void {}
public isAuthorized() {
if (this.role) {
// console.log(this.role);
return this.securityService.getRole() === this.role;
} else {
//console.log(this.securityService.isAuthenticated());
return this.securityService.isAuthenticated();
}
}
}
jwt-interceptor.services
import { SecurityService } from 'src/app/security/security.service';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class JwtInterceptorService implements HttpInterceptor{
constructor(private securityService:SecurityService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token=this.securityService.getToken();
if(token){
req=req.clone({
setHeaders:{Authorization:`Bearer ${token}`}
});
}
return next.handle(req);
}
}
security.models.ts
export interface userCredentisals
{
email:string;
password:string;
}
export interface authenticationResponse{
token:string;
expiration:Date;
}
export interface userDTO{
id:string;
email:string;
}
security.service.ts
import { Observable } from 'rxjs';
import { authenticationResponse, userCredentisals, userDTO } from './security.models';
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class SecurityService {
constructor(private http:HttpClient) { }
private apiURL=environment.apiURL+"/accounts";
private readonly expirationTokenKey:string='token-expiration';
private readonly tokenKey:string='token';
private readonly roleField:string='role';
isAuthenticated():boolean{
const token = localStorage.getItem(this.tokenKey);
if(!token){
return false;
}
const expiration=localStorage.getItem(this.expirationTokenKey);
const expirstionDate=new Date(expiration);
if(expirstionDate<=new Date){
this.logout();
return false;
}
return true;
}
getFieldFromJWT(field:string):string{
const token=localStorage.getItem(this.tokenKey);
if(!token){return '';}
const dataToken=JSON.parse(atob(token.split('.')[1]));
return dataToken[field];
}
logout(){
localStorage.removeItem(this.tokenKey);
localStorage.removeItem(this.expirationTokenKey)
}
getRole():string{
return this.getFieldFromJWT(this.roleField);
}
saveToken(authenticationResponse:authenticationResponse){
localStorage.setItem(this.tokenKey,authenticationResponse.token);
localStorage.setItem(this.expirationTokenKey,authenticationResponse.expiration.toString());
}
register(userCredentisals:userCredentisals): Observable<authenticationResponse>{
return this.http.post<authenticationResponse>(this.apiURL+"/create",userCredentisals);
}
login(userCredentisals:userCredentisals): Observable<authenticationResponse>{
return this.http.post<authenticationResponse>(this.apiURL+"/login",userCredentisals);
}
getToken(){
return localStorage.getItem(this.tokenKey);
}
getUsers(page:number, recordsPerPage:number):Observable<any>{
let params=new HttpParams();
params=params.append('page',page.toString());
params=params.append('recordsPerPage,',recordsPerPage.toString());
return this.http.get<userDTO[]>(`${this.apiURL}/listusers`,{observe:'response',params});
}
makeAdmin(userId:string){
const headers= new HttpHeaders('Content-Type: application/json');
return this.http.post(`${this.apiURL}/makeadmim`,JSON.stringify(userId),{headers});
}
removeAdmin(userId:string){
const headers= new HttpHeaders('Content-Type: application/json');
return this.http.post(`${this.apiURL}/removeadmim`,JSON.stringify(userId),{headers});
}
}
app.module.ts
import { NgModule } from '@angular/core';
import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http'
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MoviesListComponent } from './movies/movies-list/movies-list.component';
import { GenericListComponent } from './utilities/generic-list/generic-list.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
// import {LeafletModule} from '@asymmetrik/ngx-leaflet'
import {LeafletModule} from '@asymmetrik/ngx-leaflet';
// import 'leaflet/dist/images/marker-shadow.png';
////////////////////
import { icon, Marker } from 'leaflet';
const iconRetinaUrl = 'src/assets/marker-icon-2x.png';
const iconUrl = 'src/assets/marker-icon.png';
const shadowUrl = 'src/assets/marker-shadow.png';
const iconDefault = icon({
iconRetinaUrl,
iconUrl,
shadowUrl,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
});
Marker.prototype.options.icon = iconDefault;
//////////////////////////
// import * as L from 'leaflet'
// import 'leaflet/dist/leaflet.css';
// // stupid hack so that leaflet's images work after going through webpack
// import marker from 'leaflet/dist/images/marker-icon.png';
// import marker2x from 'leaflet/dist/images/marker-icon-2x.png';
// import markerShadow from 'leaflet/dist/images/marker-shadow.png';
// // delete L.Icon.Default.prototype._getIconUrl;
// L.Icon.Default.mergeOptions({
// iconRetinaUrl: marker2x,
// iconUrl: marker,
// shadowUrl: markerShadow
// });
///////
// import 'leaflet/dist/images/marker-shadow.png';
////////////////////////////////
// import img from '.'
//import { Map, latLng, tileLayer, Layer, marker, icon } from '@asymmetrik/ngx-leaflet';
// import 'leaflet/dist/images/marker-shadow.png';
///////////////////////////
import {MarkdownModule} from 'ngx-markdown';
import { MaterialModule } from './material/material.module';
import { MenuComponent } from './menu/menu.component';
import { RatingComponent } from './utilities/rating/rating.component';
import { LifecycletestComponent } from './lifecycletest/lifecycletest.component';
import { HomeComponent } from './home/home.component';
import { IndexGenresComponent } from './genres/index-genres/index-genres.component';
import { CreateGenreComponent } from './genres/create-genre/create-genre.component';
import { IndexActorsComponent } from './actors/index-actors/index-actors.component';
import { CreateActorComponent } from './actors/create-actor/create-actor.component';
import { IndexMovieTheaterComponent } from './movie-theaters/index-movie-theater/index-movie-theater.component';
import { CreateMovieTheaterComponent } from './movie-theaters/create-movie-theater/create-movie-theater.component';
import { CreateMovieComponent } from './movies/create-movie/create-movie.component';
import { EditActorComponent } from './actors/edit-actor/edit-actor.component';
import { EditGenreComponent } from './genres/edit-genre/edit-genre.component';
import { EditMovieTheaterComponent } from './movie-theaters/edit-movie-theater/edit-movie-theater.component';
import { EditMovieComponent } from './movies/edit-movie/edit-movie.component';
import { FormGenreComponent } from './genres/form-genre/form-genre.component';
import { MovieFilterComponent } from './movies/movie-filter/movie-filter.component';
import { CommonModule } from '@angular/common';
import { FormActorComponent } from './actors/form-actor/form-actor.component';
import { InputImgComponent } from './utilities/input-img/input-img.component';
import { InputMarkdownComponent } from './utilities/input-markdown/input-markdown.component';
import { MovieTheaterFormComponent } from './movie-theaters/movie-theater-form/movie-theater-form.component';
import { MapComponent } from './utilities/map/map.component';
import { FormMovieComponent } from './movies/form-movie/form-movie.component';
import { MultipleSelectorComponent } from './utilities/multiple-selector/multiple-selector.component';
import { ActorsAutocompleteComponent } from './actors/actors-autocomplete/actors-autocomplete.component';
import { DisplayErrorsComponent } from './utilities/display-errors/display-errors.component';
import { SweetAlert2Module } from '@sweetalert2/ngx-sweetalert2';
import { MovieDetailsComponent } from './movies/movie-details/movie-details.component';
import { AuthorizeViewComponent } from './security/authorize-view/authorize-view.component';
import { LoginComponent } from './security/login/login.component';
import { RegisterComponent } from './security/register/register.component';
import { AuthenticationFormComponent } from './security/authentication-form/authentication-form.component';
import { JwtInterceptorService } from './security/jwt-interceptor.service';
import { UserIndexComponent } from './security/user-index/user-index.component';
//import { ActorsComponent } from './actors/actors/actors.component';
@NgModule({
declarations: [
AppComponent,
MoviesListComponent,
GenericListComponent,
MenuComponent,
RatingComponent,
LifecycletestComponent,
HomeComponent,
IndexGenresComponent,
CreateGenreComponent,
IndexActorsComponent,
CreateActorComponent,
IndexMovieTheaterComponent,
CreateMovieTheaterComponent,
CreateMovieComponent,
EditActorComponent,
EditGenreComponent,
EditMovieTheaterComponent,
EditMovieComponent,
FormGenreComponent,
MovieFilterComponent,
FormActorComponent,
InputImgComponent,
InputMarkdownComponent,
MovieTheaterFormComponent,
MapComponent,
FormMovieComponent,
MultipleSelectorComponent,
ActorsAutocompleteComponent,
DisplayErrorsComponent,
MovieDetailsComponent,
AuthorizeViewComponent,
LoginComponent,
RegisterComponent,
AuthenticationFormComponent,
UserIndexComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MaterialModule,
ReactiveFormsModule,
CommonModule,
FormsModule ,
HttpClientModule,
MarkdownModule.forRoot(),
LeafletModule,
SweetAlert2Module.forRoot()
],
providers: [{
provide:HTTP_INTERCEPTORS,
useClass:JwtInterceptorService,
multi:true
}],
bootstrap: [AppComponent]
})
export class AppModule { }
login.component.html
<app-display-errors [errors]="errors"></app-display-errors>
<app-authentication-form [action]="'Login'" (onSubmit)="login($event)"></app-authentication-form>
login.component.ts
import { parseWebAPIErrors } from 'src/app/utilities/utils';
import { Router } from '@angular/router';
import { userCredentisals, authenticationResponse } from './../security.models';
import { SecurityService } from './../security.service';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
constructor(private securityService:SecurityService,private router:Router) { }
errors:string[]=[];
ngOnInit(): void {
}
login(userCredentisals:userCredentisals){
this.securityService.login(userCredentisals).subscribe(authenticationResponse=>{
this.securityService.saveToken(authenticationResponse);
this.router.navigate(['/']);
},error=>this.errors=parseWebAPIErrors(error));
}
}
I have posted this code in stackblitz as a node project cuz under my free trial subscription in stackblitz I am unable to post the project from github as angular, all the codes in stackbliz are under app folder. Please forgive me for this - everything is new to me - angular, stackblitz and github. I haven't posted WEBAPI project cuz I can't post the DB and WEBAPI projects in something like stackblitz, so only the angular code is available stackblitz as node project. I have debugged the WEBAPI and agular code in VSCODE as well, there are no errors - WEBAPI returns the role as "admin" to the angular project but the menu items aren't displayed after user is authenticated as role -:admin". What am I missing here?
Here's the stackblitz link Project in stackblitz
I forgot to put the value claimtype and claim value and the userId in the [AspNetUserClaims]
table of .net Identity. I am using JSON web token jwtToken for the WebAPI and adding the claim from the database to the token and the token is used to check in angular app if user is admin to manipulate the menu.