Building an inventory system for the Skinet app in the .net and angular course – Part 5
In this section we will configure the Angular app to only allow users with the “Admin” role to access the admin feature in our app. It’s important to point out that there really is no such thing as “client side security”. All of our client side code (Angular) is downloaded by the client and all of it is accessible – the true security here is provided by the API. That said, we can do a pretty good job of hiding things from users who are not in that role, but this should not be used as the only means of securing the app.
First of all we need to resolve a bug! In a previous post we added the ability to edit a product, but we did not do anything with the images. This means that since when we get a product from the API, it has a URL, but we then send up the product to the API it already has the full URL which causes an issue as we can see here:
So in order to fix this we just need to add a line to the UpdateProduct method in the ProductsController so that we set the PictureUrl to whatever it was set to already:
[HttpPut("{id}")]
[Authorize(Roles = "Admin")]
public async Task<ActionResult<Product>> UpdateProduct(int id, ProductCreateDto productToUpdate)
{
var product = await _unitOfWork.Repository<Product>().GetByIdAsync(id);
productToUpdate.PictureUrl = product.PictureUrl;
_mapper.Map(productToUpdate, product);
_unitOfWork.Repository<Product>().Update(product);
var result = await _unitOfWork.Complete();
if (result <= 0) return BadRequest(new ApiResponse(400, "Problem updating product"));
return Ok(product);
}
This wil fix the bug. Now onto the Angular app to introduce the “security” we will be adding here.
First, lets add a new ReplaySubject observable so that we can store a boolean that tells us if a user is an admin in the account.service.ts:
export class AccountService {
baseUrl = environment.apiUrl;
private currentUserSource = new ReplaySubject<IUser>(1);
currentUser$ = this.currentUserSource.asObservable();
private isAdminSource = new ReplaySubject<boolean>(1);
isAdmin$ = this.isAdminSource.asObservable();
Then lets add a method to check to see if the user is an admin or not. We can decode the jwt token using the atob method and since we know the JWT is split into 3 parts separated by a period, we can use the javascript split method to separate it into an array, then get the middle part, which contains the claims for the user:
isAdmin(token: string): boolean {
if (token) {
const decodedToken = JSON.parse(atob(token.split('.')[1]));
if (decodedToken.role.indexOf('Admin') > -1) {
return true;
}
}
}
With this in place we can then set the ReplaySubject when a user logs in or when we load the current user to which will check the user token and then set the isAdmin observable to true if they have this role:
loadCurrentUser(token: string) {
if (token === null) {
this.currentUserSource.next(null);
return of(null);
}
let headers = new HttpHeaders();
headers = headers.set('Authorization', `Bearer ${token}`);
return this.http.get(this.baseUrl + 'account', {headers}).pipe(
map((user: IUser) => {
if (user) {
localStorage.setItem('token', user.token);
this.currentUserSource.next(user);
this.isAdminSource.next(this.isAdmin(user.token));
}
})
);
}
login(values: any) {
return this.http.post(this.baseUrl + 'account/login', values).pipe(
map((user: IUser) => {
if (user) {
localStorage.setItem('token', user.token);
this.currentUserSource.next(user);
this.isAdminSource.next(this.isAdmin(user.token));
}
})
);
}
Now we have this we can create a new guard called admin.guard.ts that uses this to check if the user is and admin:
import {Injectable} from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
import {AccountService} from '../../account/account.service';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AdminGuard implements CanActivate {
constructor(private accountService: AccountService, private router: Router) {
}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> {
return this.accountService.isAdmin$.pipe(
map(admin => {
if (admin) {
return true;
}
this.router.navigateByUrl('/');
})
);
}
}
Then we can add this to the app-routing.module.ts to protect the user from getting into the admin route:
{
path: 'admin',
canActivate: [AuthGuard, AdminGuard],
loadChildren: () => import('./admin/admin.module')
.then(mod => mod.AdminModule), data: { breadcrumb: 'Admin' }
},
Note that we can use multiple guards. These are evaluated in order and only if the all guards are returned as true does the user proceed. Now we just need to hide the Admin link in the nav bar if the user is not an admin and/or not logged in. We will make use of the async pipe again so the nav.component.ts will look like this:
export class NavBarComponent implements OnInit {
basket$: Observable<IBasket>;
currentUser$: Observable<IUser>;
isAdmin$: Observable<boolean>;
constructor(private basketService: BasketService, private accountService: AccountService) { }
ngOnInit() {
this.basket$ = this.basketService.basket$;
this.currentUser$ = this.accountService.currentUser$;
this.isAdmin$ = this.accountService.isAdmin$;
}
Then in the nav.component.html we can just use an *ngIf statement to hide the link:
<a *ngIf="(isAdmin$ | async)" class="p-2" routerLink="/admin" routerLinkActive="active">Admin</a>
We now have the admin section of our client app “protected”, or hidden at least anyway. Progress.
The changes for this commit can be found here.
Next up, we will take a look at dealing with the product images.
Tags In
8 Comments
Leave a Reply Cancel reply
Recent Posts
- Building an inventory system for the Skinet app in the .net and angular course – Part 9
- Building an inventory system for the Skinet app in the .net and angular course – Part 8
- Building an inventory system for the Skinet app in the .net and angular course – Part 7
- Building an inventory system for the Skinet app in the .net and angular course – Part 6
- Building an inventory system for the Skinet app in the .net and angular course – Part 5
Will we get this in udemy course as well
Hi Nav,
I’m trying a different approach to adding features that students ask for – if there is significant demand then it’s possible I will record these as video lessons but in the short term (and once this series of tutorials is complete) I will point students to these tutorials as it is faster to get this content out there.
actually u r right sir about it this can be faster what else is left in inventory section and when can we accept it.
after that skinet will surely be one complete project with best practices to follow.
Also will it be good idea that if after completing inventory section u could create another git hub repo where students can futher exted the application and contribute to the project that will be some thing unique i will definatetely contribute there once complete inventory section is done and i complete that on my end .
I think it will be great to do,
Also thanks for producing sunch good programs in .net very few contribute in .net space.Thanks SIR 🙂
Hi Nav,
The next parts will focus on image uploading for the products, then following that it will deal with stock management and quantities, hopefully within the next few weeks or so (the image upload should be complete very soon). I do really love the idea of providing a space where students can showcase their own designs and what they have done to extend the app or add new features – I’ll have a think about the best way to do this 🙂
Hi Niel, I’m trying to find the StudentAssets.zip. Can’t find it. Where to download this file please?
Hi,
This is attached to the course contents in Udemy for the course that these tutorials are extending. If you are not a student of that course then you can grab the assets folder from the bottom of the first tutorial as a zip file.
Thanks for the info😀, let me check later. Im bought the course, actually 2 course i bought already, one is the DatingApp.
Thanks for the info😀, let me check later. I bought the course, actually 2 courses i bought already, one was the DatingApp.