MusicStore – Part5 – Authorization
15 Mar 2019Let’s authorize users by their types - ONLY “Musician” can access to Values API.
-
Include UserType in claims in IdentityServer4
// ProfileService.cs public Task GetProfileDataAsync(ProfileDataRequestContext context) { var user = _userManager.GetUserAsync(context.Subject).Result; if (user != null) { var claims = new List<Claim>(); claims.Add(new Claim(AuthorizeClaim.FirstName, string.IsNullOrEmpty(user.FirstName) ? string.Empty : user.FirstName)); claims.Add(new Claim(AuthorizeClaim.LastName, string.IsNullOrEmpty(user.LastName) ? string.Empty : user.LastName)); claims.Add(new Claim(AuthorizeClaim.UserType, user.UserType.ToString())); context.IssuedClaims.AddRange(claims); } return Task.FromResult(0); }
-
Add authorization policies in WebAPI
Policies can be built by claim, role, scope or assertion with conditions.
// Startup.cs public void ConfigureServices(IServiceCollection services) { var svcBuilder = services .AddMvcCore() .AddApiExplorer() .AddJsonFormatters(); svcBuilder = svcBuilder.AddAuthorization( options => BuildAuthorizationPolicies(options) ); ...... } private void BuildAuthorizationPolicies(AuthorizationOptions options) { //Basic Policies foreach (var userType in Enum.GetNames(typeof(EnumUserType))) { options.AddPolicy(userType, policy => policy.RequireClaim(AuthorizeClaim.UserType, userType)); } }
-
Add policy to ValuesController to ONLY allow “Musician”
[Authorize(Policy = AuthorizePolicy.Musician)] [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { ...... }
-
Test
Login with Microsoft account will return 403 when clicking on Sample, as external users are “Audience”.
However, Alice can get results from Values API, because she belongs to “Musician”.
-
Lock down permissions for WebUI
-
Add authguard.service.ts to validate UserType from claims
// authguard.service.ts import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { OAuthService } from 'angular-oauth2-oidc'; @Injectable() export class AuthGuardService implements CanActivate { constructor( private _oauthService: OAuthService, private _router: Router ) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { var isRoleValid = this._oauthService.hasValidIdToken(); if (isRoleValid) { var expectedRole = route.data.expectedRole; var claims = this._oauthService.getIdentityClaims(); if (claims && expectedRole) { if (expectedRole.indexOf(",") >= 0) { var roleArray = expectedRole.split(","); isRoleValid = roleArray.indexOf(claims["UserType"]) >= 0 } else { isRoleValid = claims["UserType"] == expectedRole; } } } if (!isRoleValid) { alert("Sorry, you don't have access to this module!"); this._router.navigate(['/']); } return isRoleValid; } }
-
Apply AuthGuard service to validate role for sample component
// app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { RouterModule } from '@angular/router'; //OAuth2 OIDC Client import { OAuthModule } from 'angular-oauth2-oidc'; //Components import { AppComponent } from './app.component'; import { NavMenuComponent } from './nav-menu/nav-menu.component'; import { HomeComponent } from './home/home.component'; import { CounterComponent } from './counter/counter.component'; import { FetchDataComponent } from './fetch-data/fetch-data.component'; import { SampleComponent } from './sample/sample.component'; //Http Interceptor import { TokenInterceptor } from './http/token.interceptor'; //Services import { UserProfileService } from './userprofile/userprofile.service'; //Security import { AuthGuardService } from './security/authguard.service'; @NgModule({ declarations: [ AppComponent, NavMenuComponent, HomeComponent, CounterComponent, FetchDataComponent, SampleComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), HttpClientModule, FormsModule, OAuthModule.forRoot(), RouterModule.forRoot([ { path: '', component: HomeComponent, pathMatch: 'full' }, { path: 'counter', component: CounterComponent }, { path: 'fetch-data', component: FetchDataComponent }, { path: 'sample', component: SampleComponent, canActivate: [AuthGuardService], data: { expectedRole: 'Musician' } } ]) ], providers: [ AuthGuardService, UserProfileService, { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true, } ], bootstrap: [AppComponent] }) export class AppModule { }
-
-
Test again
Login as Microsoft account will see this error message, as it’s been blocked by AuthGuard from front-end part.