Having roles is always a good practice to increase security for our applications and protect some resources from even logged-in users. That said, in this article, we are going to implement the Angular Role-Based authorization and learn how to work with roles on both Angular and ASP.NET Core side.
Additionally, we are going to make use of Guards to create more secure route protections. We will learn how to use Guards and how to modify them to support roles.
Lastly, we are going to show you how to preserve the authentication state of our Angular application even if the user refreshes the page.
For complete navigation through the entire series, you can visit the Angular with ASP.NET Core Identity page.
Let’s get down to business.
Using Angular Guards to Secure Routes
Right now, if the user is not authorized but tries to access the Companies link, they will be redirected to the Login page. But for this to happen, the HTTP request has to reach the API, and our interceptor has to process the 401 response and navigate the user to the Login page. Well, we can make this more efficient by using Angular guards.
The first thing we have to do is to install the angular2-jwt
library:
npm i @auth0/angular-jwt
Now, we can modify the app.module.ts
file:
... import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { JwtModule } from "@auth0/angular-jwt"; ... import { NotFoundComponent } from './error-pages/not-found/not-found.component'; export function tokenGetter() { return localStorage.getItem("token"); } @NgModule({ declarations: [ ... ], imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot([ ... ]), JwtModule.forRoot({ config: { tokenGetter: tokenGetter, allowedDomains: ["localhost:5001"], disallowedRoutesRoutes: [] } }) ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerService, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
With this in place, we can create our Guard service:
ng g guard shared/guards/auth --skip-tests
And choose to implement only the CanActivate
interface.
Now, we can modify the authentication service:
... import { JwtHelperService } from '@auth0/angular-jwt'; @Injectable({ providedIn: 'root' }) export class AuthenticationService { private _authChangeSub = new Subject<boolean>() public authChanged = this._authChangeSub.asObservable(); constructor(private http: HttpClient, private envUrl: EnvironmentUrlService, private jwtHelper: JwtHelperService) { } ... public isUserAuthenticated = (): boolean => { const token = localStorage.getItem("token"); return token && !this.jwtHelper.isTokenExpired(token); } private createCompleteRoute = (route: string, envAddress: string) => { return `${envAddress}/${route}`; } }
And then, we can modify the guard:
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { AuthenticationService } from '../services/authentication.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private authService: AuthenticationService, private router: Router){} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if (this.authService.isUserAuthenticated()) { return true; } this.router.navigate(['/authentication/login'], { queryParams: { returnUrl: state.url }}); return false; } }
As you can see, in this guard we use the state.url
property to set it as a query parameter for our returnUrl
property in the Login component. All the other parts of this service are already explained in the mentioned article.
After this, we can protect our route in the app.module.ts
file:
RouterModule.forRoot([ { path: 'home', component: HomeComponent }, { path: 'company', loadChildren: () => import('./company/company.module').then(m => m.CompanyModule), canActivate: [AuthGuard] }, { path: 'authentication', loadChildren: () => import('./authentication/authentication.module').then(m => m.AuthenticationModule) }, { path: '404', component : NotFoundComponent}, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/404', pathMatch: 'full'} ]),
Testing the Guard
If we start the Angular app and try to navigate to the Companies page, we are going to see the Login page:
But this time, we didn’t send the request to the API, and also, we have the returnUrl
parameter in the URI.
As soon as we enter valid credentials, the application navigates us to the Companies page and not to the Home Page. Moreover, we can see all the companies. This means the token is included in the HTTP request.
Adding Roles to the Web API
We already support roles in the Identity configuration:
services.AddIdentity<User, IdentityRole>(opt => { opt.Password.RequiredLength = 7; opt.Password.RequireDigit = false; opt.User.RequireUniqueEmail = true; })
So, all we have to do is to add default roles in the database and include the role claim in the JWT.
First, let’s add a role configuration class in the Entities/Configuration
folder:
public class RoleConfiguration : IEntityTypeConfiguration<IdentityRole> { public void Configure(EntityTypeBuilder<IdentityRole> builder) { builder.HasData( new IdentityRole { Name = "Viewer", NormalizedName = "VIEWER" }, new IdentityRole { Name = "Administrator", NormalizedName = "ADMINISTRATOR" } ); } }
Then, we have to modify the OnModelCreating
method and run commands to create and execute migration:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new CompanyConfiguration()); modelBuilder.ApplyConfiguration(new EmployeeConfiguration()); modelBuilder.ApplyConfiguration(new RoleConfiguration()); }
PM> Add-Migration InitialRoleSeed PM> Update-Database
Also, because we have one user in the database, let’s connect that user to the Administrator role:
INSERT INTO AspNetUserRoles VALUES ('48d668ff-3d3e-4999-9592-ed116b69413b','91b8dbdd-50ed-4a9e-9bba-99ec95616787')
Of course, you will have different Id values for the User and the Role.
Modifying Register Action and Claims
After these changes, we are going to modify the RegisterUser
action to include Visitor role for new users:
[HttpPost("Registration")] public async Task<IActionResult> RegisterUser([FromBody] UserForRegistrationDto userForRegistration) { if (userForRegistration == null || !ModelState.IsValid) return BadRequest(); var user = _mapper.Map<User>(userForRegistration); var result = await _userManager.CreateAsync(user, userForRegistration.Password); if (!result.Succeeded) { var errors = result.Errors.Select(e => e.Description); return BadRequest(new RegistrationResponseDto { Errors = errors }); } await _userManager.AddToRoleAsync(user, "Viewer"); return StatusCode(201); }
And let’s populate claims by modifying the JwtHandler
class:
private readonly IConfiguration _configuration; private readonly IConfigurationSection _jwtSettings; private readonly UserManager<User> _userManager; public JwtHandler(IConfiguration configuration, UserManager<User> userManager) { _userManager = userManager; _configuration = configuration; _jwtSettings = _configuration.GetSection("JwtSettings"); } ... public async Task<List<Claim>> GetClaims(User user) { var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.Email) }; var roles = await _userManager.GetRolesAsync(user); foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); } return claims; }
Here, we inject the UserManager
class and use it to find all the user’s roles and add them to a list of claims.
Finally, in the Login action, we have to modify the call to the GetClaims
method because it is now an async one:
var claims = await _jwtHandler.GetClaims(user);
That’s all it takes. We can move on to implement the Angular Role-Based authorization.
Implementing Angular Role-Based Authorization Part
To implement the Angular Role-Based authorization, we have to create a component that we are going to protect with roles. So, let’s create a privacy component:
ng g c privacy --skip-tests
After the component creation, we are going to create a simple logic to fetch the claims from the Web API’s action:
export class PrivacyComponent implements OnInit { public claims: [] = []; constructor(private _repository: RepositoryService) { } ngOnInit(): void { this.getClaims(); } public getClaims = () =>{ this._repository.getClaims('api/companies/privacy') .subscribe(res => { this.claims = res as []; }) } }
Of course, we have to add a new method in the repository file:
public getClaims = (route: string) => { return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress)); }
Also, we need to slightly modify the privacy HTML file:
<section style="margin-left: 15px;"> <h2>List of Claims</h2> <ul> <li *ngFor="let claim of claims"> {{claim.type}} : {{claim.value}} </li> </ul> </section>
Now, we have to add the route in the app.module.ts
file:
RouterModule.forRoot([ { path: 'home', component: HomeComponent }, { path: 'company', loadChildren: () => import('./company/company.module').then(m => m.CompanyModule), canActivate: [AuthGuard] }, { path: 'authentication', loadChildren: () => import('./authentication/authentication.module').then(m => m.AuthenticationModule) }, { path: 'privacy', component: PrivacyComponent }, { path: '404', component : NotFoundComponent}, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/404', pathMatch: 'full'} ]),
And let’s modify menu.component.html
file:
<li class="nav-item"> <a class="nav-link" [routerLink]="['/privacy']" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}"> Privacy </a> </li>
Finally, for this to work, we have to create action on the server-side. We are going to create it in the Companies controller:
[HttpGet("Privacy")] [Authorize] public IActionResult Privacy() { var claims = User.Claims .Select(c => new { c.Type, c.Value }) .ToList(); return Ok(claims); }
Here, we just return all the claims to the client application in a key-value format.
Now, if we start both applications and log in successfully, we can visit the Privacy page and see our claims:
Of course, if you are not logged in and try to click the Privacy link, you will be directed to the Login page. You won’t be able to see the returnUrl parameter because we are not handling it in our Interceptor service. But if you want to implement that, all you have to do is to add a small change in the handleUnauthorized
function:
private handleUnauthorized = (error: HttpErrorResponse) => { if(this.router.url === '/authentication/login') { return 'Authentication failed. Wrong Username or Password'; } else { this.router.navigate(['/authentication/login'], { queryParams: { returnUrl: this.router.url }}); return error.message; } }
Now, let’s modify the [Authorize]
attribute for the Privacy action to support roles:
[HttpGet("Privacy")] [Authorize(Roles = "Administrator")] public IActionResult Privacy()
With this modification, we state that only Administrators can access this action.
If we register a new “test user” for our application, log in with that user and try to access the Privacy page, we are going to get an error message:
This means access is forbidden for the user. Since our new user has the Viewer role, it is clear why we get this type of error.
This works as expected but, we don’t want this type of behavior. The better approach to handling this is to have a Forbidden page.
Handling Angular Role-Based Authorization Error with a Forbidden Page
Let’s start with the component creation:
ng g c forbidden --skip-tests
Then, we can add the route to this component in the app.module.ts
file:
{ path: 'forbidden', component: ForbiddenComponent },
Now, we can modify the forbidden.component.ts
file:
import { Component, OnInit } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-forbidden', templateUrl: './forbidden.component.html', styleUrls: ['./forbidden.component.css'] }) export class ForbiddenComponent implements OnInit { private returnUrl: string; constructor( private router: Router, private route: ActivatedRoute) { } ngOnInit(): void { this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; } public navigateToLogin = () => { this.router.navigate(['/authentication/login'], { queryParams: { returnUrl: this.returnUrl }}); } }
This is a pretty simple logic where we fetch the returnUrl
from the ActivatedRoute
class and in the navigateToLogin
function, we just navigate to the Login page with the query parameter.
After this, we have to modify the forbidden.component.html
page:
<h2>Forbidden</h2> <p> This page is forbidden for you. Please <button class="btn btn-link" (click)="navigateToLogin()">log in with a different user</button> to access the requested page. </p>
Of course, we have to handle this in the error handler service and with the guard as well.
Let’s first start with the error-handler.service.ts
file modification:
private handleError = (error: HttpErrorResponse) : string => { if(error.status === 404) { return this.handleNotFound(error); } else if(error.status === 400) { return this.handleBadRequest(error); } else if(error.status === 401) { return this.handleUnauthorized(error); } else if(error.status === 403) { return this.handleForbidden(error); } } private handleForbidden = (error: HttpErrorResponse) => { this.router.navigate(["/forbidden"], { queryParams: { returnUrl: this.router.url }}); return "Forbidden"; }
Now, if we log in with a test user and try to access the privacy page, we are going to see the Forbidden page with the returnUrl parameter:
Of course, as soon as we log in with the administrator’s credentials, we are going to see the Privacy page.
Using Guards to Implement the Angular Role-Based Authorization
We can improve our Angular Role-Based authorization by using Guards and thus preventing the request to reach the server at all.
To implement this, we can modify the existing guard file or we can create a new one.
In this article, we are going to create a new one.
So, let’s start with a creation:
ng g guard shared/guards/admin --skip-tests
And choose the CanActivate
implementation.
Then, let’s add a new function to the authentication.service.ts
file:
public isUserAdmin = (): boolean => { const token = localStorage.getItem("token"); const decodedToken = this.jwtHelper.decodeToken(token); const role = decodedToken['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] return role === 'Administrator'; }
Here we decode our token, extract the role, and return the comparison result.
Now, we can implement the guard file:
import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { AuthenticationService } from '../services/authentication.service'; @Injectable({ providedIn: 'root' }) export class AdminGuard implements CanActivate { constructor(private authService: AuthenticationService, private router: Router) {} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { if(this.authService.isUserAdmin()) return true; this.router.navigate(['/forbidden'], { queryParams: { returnUrl: state.url }}); return false; } }
And, let’s protect the Privacy
route:
{ path: 'privacy', component: PrivacyComponent, canActivate: [AuthGuard, AdminGuard] },
Great.
Let’s try it now:
As we can see, if we try to navigate to the Privacy page without logging in, we are going to be redirected to the Login page. If we enter the test user’s credentials, we are going to be navigated to the Forbidden page. But if we log in with a user with sufficient rights, we can see the Privacy page.
Excellent.
This works like a charm.
Preserving Authentication State in the Angular Application
Right now if we log in and right away refresh our application, we are going to see the Login and Register links, even though we have the valid token (not expired) in the Local Storage.
Of course, we can leave it as-is and make the user repeat the Login action. But, if we want to preserve the authentication state of the application, we have to make a few changes.
First, let’s modify the menu.component.ts
file:
constructor(private authService: AuthenticationService, private router: Router) { this.authService.authChanged .subscribe(res => { this.isUserAuthenticated = res; }) }
After this change, we have to modify the app.component.ts
file:
import { Component, OnInit } from '@angular/core'; import { AuthenticationService } from './shared/services/authentication.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'AngularClient'; constructor(private authService: AuthenticationService){} ngOnInit(): void { if(this.authService.isUserAuthenticated()) this.authService.sendAuthStateChangeNotification(true); } }
Here, as soon as our component initializes, we check if the user is authenticated. If the check passes, we sand the observable notification to the menu component.
And that’s it.
Now, we can log in and refresh the page. We are still going to see just the Logout link. Of course, if we do the same action after 5 minutes, we are going to see the Login and Register links because our token is not valid anymore.
Conclusion
There we go.
We have covered a lot of ground here and learned how to implement roles in our Angular application. Also, we have learned how to use interceptors and guards to increase the protection of our application.
In the next article, we are going to learn how to implement the Password Reset functionality with Angular and ASP.NET Core Identity.
So, see you there.
Hi. Thanks for the article and the whole series, it is really helpful!
right now I´m using .net 6 and angular 14, and when decoding the token i get all info right but the roles, I mean ” decodedToken[‘http://schemas.microsoft.com/ws/2008/06/identity/claims/role’]” is always undefined, nevertheless the token works on all other functions,
My output has a form like the following “{{“alg”:”HS256″,”typ”:”JWT”}.{“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name”:”[email protected]”,”http://schemas.microsoft.com/ws/2008/06/identity/claims/role”:”Admin”,”exp”:1673140826,”iss”:”CodeMazeAPI”,”aud”:”https://localhost:5011″}}”
Thanks in advance
Sorry, my question above is wrong, the string I pasted is the “tokenoptions” variable before generating token in the .net (api) server-side. Nevertheless when the token is decoded in client-side. Role claim is missing, it is just gone. SO different issue but not working yet.
Is this a known erroe for you?
Hi Hugo, to be honest, the best I can do is to ask you – have you tried to download our source code and compare it with yours? This should help you as our project works, at least the last time I checked it worked. You should be getting the Role claim in your JWT.
Hi, if I want to take the AspNetUsers Id as a foreign key in another table how can I do? I have linked the relationship with the parent and child table but the child table can’t insert the parent table primary key.
Well, I am not sure why you have issues with that. If you created a foreign key column with the same type as the parent table’s column, it should be a straight forward process. If you didn’t modify anything, the column type should be nvarchar.
I see~ I have found out I make a mistake on the entity part. Thank you for your information!
what if I define the get token method within auth service. how could I call it from NgModule decorator to configure the jwt tokenGetter? Otherwise I need to write multiple localstorage.get methods which is not good.
For this very purpose, just leave it in the module file. It is a simple helper function that jwt service uses to attach that token to any request.
thanks for your reply!
I also found a possible solution from official repo https://github.com/auth0/angular2-jwt/issues/669
have a nice day
Awesome, thanks for sharing this link. Yeah, this factory looks useful for sure.
Do you have any idea about the privacy claims can’t show after adding the [Authorize]?
I didn’t create the RoleConfiguration file and inherit OnModelCreating in my dbContext because I manually created the tables in db without running the add-migration and update-database. I can login and the privacy claims is showing after removing the [Authorize]
You must have the [Authorize] attribute as this proves that your user is authorized. If you can’t enter the action with that attribute activated it means that your authentication mechanism is not working well. I suggest you download our source code and compare it with yours as I really can’t tell what is the issue here.
Alright, Thank you and this is a great article.
me again
when i implement the privacy component in the html section the
claim.type gives an error of type error does not exist on never and the complier error is
Error: src/app/privacy/privacy.component.html:6:21 – error TS2339: Property ‘type’ does not exist on type ‘never’.
6 {{claim.type}} : {{claim.value}
~~~~
src/app/privacy/privacy.component.ts:6:16
6 templateUrl: ‘./privacy.component.html’,
~~~~~~~~~~~~~~~~~~~~~~~~~~
Error occurs in the template of component PrivacyComponent.
I am really not sure about this one. Try to debug your getClaims function to see what you get from the server. Also, download our source code, and compare it to yours to see if you maybe missed anything.
When i execute the insert into command i get the error
The INSERT statement conflicted with the FOREIGN KEY constraint “FK_AspNetUserRoles_AspNetRoles_RoleId”. The conflict occurred in database “IdentityDb”, table “dbo.AspNetRoles”, column ‘Id’.
You need to use your own values for the ids. Yours are different for sure.
These ids are to be the ids of the registered user?
Yes. The Id of the registered user and the Id of Admin role
Hey..What are the passwords used in this article?
This article is a part from the Angular Security series. We’ve created na new user earlier in the series. But you can create any user you want. The best thing to do is to visit the link at the begining of the article that leads to the page with all the articles from the series, and check them in the order they are placed.
I went through all the articles of the series, still I didn’t get the correct username and password. All those passwords mentioned in the articles give errors. Can you mention it please…?
In this article https://code-maze.com/user-registration-angular-aspnet-identity/ we create registration functionality and register our user with username and password. So, you have to do the same. Register our user first and then try to log in and add roles.
Thank you so much. I registered giving those details and logged in once. But after log out, I cannot login giving same username, password. Why is that ? Also, I cannot figure the place where you assigned roles.
Now login issue is ok.. Could you mention the place where you assigned roles? When I login as Code Maze, it says that privacy page is forbidden.
In this article, when we create a RoleConfiguration class and apply migration, we already have a user in the database, so we manually assign it to the administrator role… You can find the code for that in this article. Also, with this example (from this article) whenever you register a new user it will receive a Viewer role and won’t be able to access the Privacy page. You have manually to assign the admin role to that user. Everything is explained in this article… But my suggestion is if you want to learn this properly, to start from the first article of the series and move on step-by-step.
Thank you very much for this article, it was really helpful especially for beginners like me 🙂
Just a remark about tokenGetter, it displays an error when declarig it this way:
config:{
tokenGetter: tokenGetter()
}
As it is defined in angular-jwt.module this way:
tokenGetter?: (request?: HttpRequest<any>) => string | null | Promise<string | null>;
So the right way to write it is :
tokenGetter: ()=>{return tokenGetter()},
Hello, and thank you for the kind words, it means a lot when our article is helpful to our readers.
Now, regarding your suggestion, I am not sure about it, since we are not cooling the tokenGetter function in the way you described.
config: {
tokenGetter: tokenGettter
}
is the right way, without parentheses.
So, you can either download our source code to see that it is working, or you can visit the official page for the library: https://www.npmjs.com/package/@auth0/angular-jwt and see the example they use – which is the same as we use.
Hi, Can you post an article where we create Roles Dynamically on UI and also assign roles and claims(CRUD operation) by clicking on the user, we get to a new page where we give roles and claims to that specific user? Thank you.
with Angular & Asp.Net Core Identity
Those articles are awesome, thank you! I’m wondering, how would you implement some roles per ressources(like if you can be “user” for one company, and “admin” for another one?
Users can have multiple roles. You can assign both roles to a single user and check-in code whether they have a required role. The UserRoles table from the ASP.NET Core Identity scheme serves specifically for that purpose.
Okay, let’s admit that we have other roles, like “Company_Admin”, but then, on Asp.Net, how would that work with the
[Authorize(Roles="???")]
Maybe I can’t see the issue from your perspective, but from mine, I see no issue there at all. If you want to allow access only to the User role for that controller you should use [Authorize(Roles=”User”] for the next one that you want to allow access only to admins: [Autorize(Roles=”Admin”)], also if in your controller you have different actions and you want to allow access for Users for some actions and Admins for different ones, you can add the Authorize attribute with the Roles property on top of each action. Finally, if you want to use a single Roles property with multiple roles, you can just separate them with a comma: Roles=”Users,Admin”
On my API, let’s say have a method like:
But yours users can be reader/admin of the company A but not the company B.
How do you ensure that the users that only have Read role for company A cannot see data from company B? Or if a user is admin for company A, but reader for company B, how do you ensure it cannot call “Edit” endpoint for company B?
What I mean is that the same method, called by the same user, should succeed or fail depending on the data on which it’s performed. And those CompanyA/CompanyB are of course dynamic business data coming from the DB, not 2 different controller/methods.
Ps, just baught your ultimate asp.net core web collection 🙂
Maybe in that case it is better to use polices where you can provide multiple conditions plus roles.
I’m not aware of policies in MS Identity, I will have to check this. Any reference in mind?
Since you bought our Premium package, you have an explanation for that in the Security book. Also, you can read this article: https://code-maze.com/atribute-based-access-control-blazor-webassembly-identityserver4/ to see how to implement it. Even though we use IS4 here and in the security book, it is the same implementation.
Thanks I will! I’ve quickly checked your link. I’m still not sure how to extract from the request DTO the organization ID to check which claim the user should own to allow the operation, but I will check your book before bothering you 😉
Hi again, I’ve read the main book and the security bonus. I now understand that we can created custom claim(at compilation time) that an user must have the role “Administrator” and the country “USA”, but in my case, I will assign each user to several companies, with different roles for each company. And the company ID/names are not known when developing the application, it’s a resource that will get created/deleted by the customer. So I cannot create a static policy that always require the country to be USA. With the country example, I would need to say: if the “countryId” of the accessed ressource match the user country, then it’s accepted.
Also, second problem, in my case they will be assigned to multiple companies.
Another unrelated question: can we really rely on the claim for such things? Isn’t there a risk that the claim has changed since the token generation?
Hello Jaytonic. Well, to be honest, when using claims, you must be sure that only the admin changes that data and not the customer. The customer can provide that info maybe the first time they register but those shouldn’t be modified by users but only admins. In our example, we made it simple and used country but you will probably use some more meaningful data that can be accessed only by administrators. Now for your example, I am not quite sure what to do there. If each of your companies has its own API, then this is not a problem, since you can protect each API with IdentityServer and provide required constraints for the users. But if you have all of that in a single API, then I am not quite sure. It will require some custom logic for sure.
Yeah, but would this kind of custom logic fit in the asp.net core identity framework?
Because, our web app is a portal that will gather data from different factory, and we need to be able to assign the user A the “read” rights on factory b&c but “admin” rights on the facotry A. Some users will have the rights to acces all the data, either with read or with admin rights.
I mean, if you take JIRA API as an example, you access the same endpoint, but you have parameters in the URL indicating what ressources you’re trying to access/edit and you’ve to check that the user has rights on this ressource, right?
I believe it should fit. But again, I didn’t face such a problem with my apps. I am not quite sure, at this moment, how this can be resolved.
I’m loosing the track on adding Admin Role for a user. In what table did you add those Role details. Find screenshot in attachment.
Hello Danziee The table is AspUserRoles, as you can see from the Insert statement. This table connects users with their roles using primary keys from each side.
Thank you for quick response. Actually the confusion is, how will it fetch Roles from this table, as I’m not seeing AspUserRoles table name is being used anywhere after that in this current article.
This is the way to get roles for the current user:
You are using the _userManager service provided from ASP.NET Core Identity and it is doing the magic behind the scenes by connecting everything it needs.
Done with the table, named it as IdentityUserRole having two columns i.e. UserId, RoleId
Also added
in Registeration Action.
The thing is, when I register any user, details goes to Database successfully but also giving an error like.
When you say: “Done with the table, named it as IdentityUserRole”, does that mean that you created it manually? If that’s the case, don’t do it. This article is part of the series: https://code-maze.com/angular-security-with-asp-net-core-identity/ (as we’ve linked at the beginning of the article) and there you can find a first article where we integrate Identity with our app and execute migrations that creates an entire database for us.
Yes Marinko, I have been following the series from first article i.e https://code-maze.com/user-registration-angular-aspnet-identity/.
What I got is, there is two table creation done through your article.
1: Users
2: IdentityRole through RoleConfiguration
Couldnt fine any working of AspUserRoles
Stucking here for three days :/
Well, this series is just a continuation of the Identity series we have on our site. And for this series, I’ve already prepared a starting project. If you read carefully the first article, you will see that I’ve added a link to the repository and mentioned that we’ve prepared a starting project that you can use. But if you want to go with it from the scratch, you can visit the link that I’ve provided in the first article of this series that points to the first article of the Identity series: https://code-maze.com/identity-asp-net-core-project/
I’ve tried to link everything I can because it doesn’t make sense to have an article that is 5k words long.
Got the working of previous Tables… Thank You Marinko 🙂
Do you have any recomendations for dynamically adding the API endpoint to the whitelist so it can exist in the appsettings or envioronment?
I’ve really never done that. There are some examples of using env files inside the app module file, but I didn’t try any of those.
How should the api know what user User is when there is nothing in the request?
[HttpGet("Privacy")]
[Authorize]
public IActionResult Privacy()
{
var claims = User.Claims
.Select(c => new { c.Type, c.Value })
.ToList();
return Ok(claims);
}
There is, the JWT. API uses the authorization framework to do the token decription job and provide a required information.
Aha, thank you! Seems like my request header does not contain any authorization, maybe i’ll have to take a look at that!
Edit:
Had to add the headers myself:
headers: new HttpHeaders({
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.getItem("token")}`
})
Thanks for the response. I think it would be worth including this in the tutorial.
I’m not sure if the example code would even authenticate without this added to the repository service
But the token should already be included in the header due to jwt library. Check this article https://code-maze.com/authentication-aspnetcore-jwt-2/. There I’ve even shown the bearer part in the request header.
Ah, that helped me figure out my issue.
It was not clear that the JwtModule.forRoot() import was adding the bearer token to the header. In my case, I had a typo in the whitelistedDomains and did not think to look there.
Thanks for the speedy response
Thank you for a very interesting post. I have a question: how do you handle if a user has multiple roles?
Hello Ingo. I am glad you liked the article. I hope the whole series will provide you enough knowledge in this area. About your question, concerning the Web API part, it already handles that. If you inspect the JwtHandler class and GetClaims method, you will see that all the roles for the current user are placed inside the claims list and returned to the client. On the client side you have to make changes. First of all, once you decode the token, if you have more than one role claims, it will be parsed as a single role array claim. So you can’t write this: return role === ‘Administrator’; because the role is now array. What you can do is something like this: return role.includes(‘Administrator’); This will return true or false if the array contains the Administrator role inside.
I hope this helps.
Thank you for the interesting article. Just to mention two typos I found: first, before the first Privacy route in the Companies controller, even for the first time the route has to be mentioned (in HttpGet or separately). Second, in the Admin auth guard, a ‘ (apostrophe) character is missing from the line this._router.navigate([‘/forbidden], { queryParams: { returnUrl: state.url }});
Yeah, both are true. Obviously, I don’t know to copy the code from the editor to the WordPress 🙂 Or WP is just trolling me 🙂