Blazor WebAssembly - How to create Policy-Based Authorization - blazor-webassembly

I have been trying to add Authorization and permissions to my project.
I have managed to generate the database tables as such:
AspNetRoleClaims
AspNetUserClaims
AspNetRoles
AspNetUsers
ApsNetUserRoles
These tables got generated using PMC, I committed these tables after the blazer web assembly template was used.
In the PMC I entered:
update-database
Which generated those tables described above.
So when i use:
<AuthorizeView Roles="Admin">
<div class="wrapper">
<ContentLayout Title="#_greeting">
<Card>
<CardContent>
Hi #context.User.Identity!.Name
</CardContent>
</Card>
</ContentLayout>
</div>
<div>
</div>
</AuthorizeView>
Works great and only Admin can view the content.
Now what my problem is how do I add Policy-Based Authorisation, I have searched to find a solution but I tried examples but no luck.
What I'm trying to do is find a way of adding Policy-Based Authorization without any logic so it's built-in with this table, is this possible?
Or can someone please share how I can achieve Policy-Based Authorization?
These are the data in the tables:
And what is was trying for Policies:
<AuthorizeView Policy="CanBuy">
<div>hello</div>
</AuthorizeView>
But i get error:

You solution makes no sense, since each #if checks for #context.User..., and if that is not null, then user it authenticated, and AuthorizeView is redundand (without policy attribute).
I have just researched this myself, and come up with a solution (add this code in client startup):
services.AddApiAuthorization();
services.AddAuthorizationCore(options =>
{
options.AddPolicy(
"CanBuy",
policy => policy.RequireClaim(
claimType,
claimValue));
});
Putting policy on client only makes no sense, so you would need to do the same on the server side.

The accepted answer is NOT good practice.
Here's a better solution that follows the recommended approach for ASP.NET Core and Blazor applications:
Option 1: validating within bootstrapping code; suggest using this with simple validation only.
services.AddAuthorizationCore(options => {
options.AddPolicy("CanBuyPolicy", policy =>
policy.RequireClaim("permission.canbuy", "CanBuy"));
});
OR
services.AddAuthorizationCore(options => {
options.AddPolicy("CanBuyPolicy", policy =>
policy.RequireRole("admin", "user"));
});
Option 2: setup policy handlers, and bootstrap them;
Create a Policy class that does the Authorization Handling of your requirement:
public class UserCanBuyPolicy : IAuthorizationHandler
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context)
{
//claim-based validation
if (context.User.HasClaim("permission.canbuy", "CanBuy"))
context.Succeed(requirement);
//role-based validation
if (context.User.IsInRole("admin") || context.User.IsInRole("user"))
context.Succeed(requirement);
return Task.CompletedTask;
}
}
Then you need to register your policy in your application bootstrapping:
services.AddScoped<IAuthorizationHandler, UserCanBuyPolicy>();
services.AddAuthorizationCore(options => {
options.AddPolicy("CanBuyPolicy", policy => policy.Requirements.Add(UserCanBuyRequirement));
});
The latter option offers more flexibility and can be scaled to include more custom logic as needed.
HTH

I made it work with this.
<AuthorizeView>
#if (#context.User.Claims.Count(c => c.Value == Permissions.Products.Create) != 0)
{
<a class="btn btn-success" href="product/create">Create</a>
}
#if (#context.User.Claims.Count(c => c.Value == Permissions.Products.Edit) != 0)
{
<a class="btn btn-warning">Edit</a>
}
#if (#context.User.Claims.Count(c => c.Value == Permissions.Products.View) != 0)
{
<a class="btn btn-primary" href="product-list">View</a>
}
#if (#context.User.Claims.Count(c => c.Value == Permissions.Products.Delete) != 0)
{
<a class="btn btn-danger">Delete</a>
}
</AuthorizeView>
Please find the repo for your reference. https://github.com/chhinsras/PermissionBlazorApp/blob/master/PermissionBlazorApp/Client/Pages/Product/ProductPage.razor

Related

Multiple actions within a NGRX effect

While learning Angular and NGRX, ran into what I thought would be simple example of common problem but having trouble understanding best way to accomplish the control logic with in the Redux store\effects patterns.
General Process:
User types in credit card info, hit pay button > Dispatch a "GetTokenAction" from component > Make an Http request to external 3rd party API to tokenize > Submit that information if successful to Payment API
Here is my latest attempt:
#Effect() getToken$ = this.actions$
.ofType(TokenizerActions.GET_TOKEN)
.pipe(switchMap((action: TokenizerActions.GetTokenAction) => {
return this.TokenizerApiService.getToken(action.payload)
.pipe(
map(tokenResponse => {
console.log(tokenResponse);
// service will return 200 with "declined" status. In this case I want to treat it like an Error.
if (tokenResponse.results.Error != null) {
return new TokenizerActions.GetTokenFailedAction(tokenResponse.results.Error.messages);
}
// What is best practice here? Call Payment Service? Dispatch Actions? Where should this mapping logic live?
const paymentRequest: PaymentRequest = new PaymentRequest();
paymentRequest.token = tokenResponse.results.token;
paymentRequest.amount = action.payload.amount;
paymentRequest.customerNumber = action.payload.customerNumber;
paymentRequest.correlationId = tokenResponse.results.correlation_id;
// this does not work, "dispatched an invalid action: undefined" error.
mergeMap((payReq: PaymentRequest) => [new paymentActions.MakePaymentAction(paymentRequest),
new TokenizerActions.GetTokenSuccessAction(tokenResponse.results.token)]);
}),
catchError(error => of(new TokenizerActions.GetTokenFailedAction(error)))
);
}));
constructor(
private actions$: Actions,
private TokenizerApiService: TokenizerApiService,
private paymentApiService: PaymentApiService
) { }
Question/Considerations:
Is the effect the appropriate place to handle this? The first working version had the component controlling the flow and dispatching multiple actions, could also be handled within the services, not sure which is best practice.
What is the preferred method for error notification within the Effects pattern? Online and in sample application there are a lot of simple examples, but I am having trouble translating that to a slightly more complex example (inspect response and then throw errors and stop processing as needed). Currently the application is doing something like this:
<span class="error" *ngFor="let error of tokenErrors$ | async">{{error.description}}</span>
<span class="error" *ngFor="let error of paymentErrors$ | async">{{error.description}}</span>
<div class="success" *ngIf="(payment$ | async)?.transactionStatus === 'approved'">Success</div>
this.paymentErrors$ = this.store.select(state => state.payment.paymentErrorMessages);
this.tokenErrors$ = this.store.select(state => state.token.tokenErrorMessages);
this.payment$ = this.store.select(state => state.payment.paymentStatus);
Can it be simpler? Should errors be combined into one PayError array? Is there a catch in the component level if subscribed or is it all to be handled in the effects level?
First, don't do the arrow function directly and create selectors instead.
Secondly, select a ViewModel (transactionInfo) that's needed for this component with one selector instead of having 3 different ones. You can combine multiple selectors to achieve that.
The result could be pushed higher in the template with *ngIf, for example
<ng-container *ngIf="transactionInfo$ | async as transactionInfo">
<span class="error" *ngFor="let error of tokenErrors">{{error.description}}</span>
<span class="error" *ngFor="let error of transactionInfo.paymentErrors">{{error.description}}</span>
<div class="success" *ngIf="transactionInfo.transactionStatus === 'approved'">Success</div>
</ng-container>

Disabling add, edit, delete buttons

I would like to add dynamic buttons in my Angular asp.net core web api. When the status of product is "Z", functionality like add, edit and delete should be not visible.
I have one method which works but in second case I do not know how to use it. Please find below example of working method:
Component.service.ts
getDisabledAddEditDel(model:Component,mode: string)
{
if(model && mode != 'View' && mode !='Add' && model.StatusOfProduct === 'Z')
{
return false;
}
return true;
}
In above case StatusOfProduct exist in model Component. I would like to use this method in a second case but in second model, StatusOfProduct does not exist. How can I use StatusOfProduct from Component in other service (model). Is there any other way than adding StatusOfProduct to my second service and model?
Thank you.
You can add the condition directly in the html. The [disabled] directive if set to true will disable the given button.
<button [disabled]="model && mode != 'View' && mode !='Add'
&& model.StatusOfProduct === 'Z'" (click)="add()">ADD</button>
You can also achieve the same result with your function
<button [disabled]="getDisabledAddEditDel(model,mode)"
(click)="add()">ADD</button>

"This operation is insecure" when using class properties as key

I have a custom FileArgument class that I'm using to store information about an uploaded file:
export class FileArgument {
name: string;
path: string;
file: File;
}
My upload works fine and the server then returns the path where the file was uploaded. I then want to store this path in a dictionary using a previously set fileArgument.name as key. Below is a simplified overview of my component. onSubmit() is where the action is happening:
export class InputModuleComponent {
private vsmodule: InputModule;
private moduleArguments = {};
private fileArgument: FileArgument = new FileArgument();
#Input()
module: InputModule;
constructor(private inputModuleService: InputModuleService) {}
onSubmit(): void {
this.inputModuleService.postFile(this.fileArgument.file).subscribe(
response => {
this.moduleArguments[this.fileArgument.name] = response.filename;
console.log(this.moduleArguments);
},
error => {}
);
}
onFileChange(event): void {
this.fileArgument.file = event.originalTarget.files[0];
this.fileArgument.name = event.originalTarget.id;
}
}
Line 14 above (this.moduleArguments[this.fileArgument.name] = response.filename;) causes the following error in Firefox:
EXCEPTION: Uncaught (in promise): SecurityError: The operation is insecure.
and in Chrome:
core.umd.js:5995 EXCEPTION: Uncaught (in promise): InvalidStateError: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.
If I replace that line with, for example:
this.moduleArguments['hello'] = response.filename;
I don't get any errors. The error clearly comes from using fileArgument.name as a dict key, but I have no idea why.
EDIT: The postFile() method from my service is below:
postFile (file: File): Observable<any> {
console.log('input-module.service - postFile()');
var url = this.uploadURL;
return Observable.create(observer => {
var formData: FormData = new FormData()
var xhr: XMLHttpRequest = new XMLHttpRequest();
formData.append("upload", file, file.name);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
The component HTML:
<a (click)="modal.open()">
{{vsmodule.displayName}}
</a>
<modal #modal>
<form (ngSubmit)="onSubmit()">
<modal-header [show-close]="true">
<h4 class="modal-title">Input Module - {{vsmodule.displayName}}</h4>
</modal-header>
<modal-body>
<p>{{vsmodule.description}}</p>
<hr>
<ul>
<li *ngFor="let arg of vsmodule.args; let i = index">
<fieldset *ngIf="arg.type == 'file'">
<label>{{ arg.displayName }}</label>
<input
name="{{arg.name}}"
id="{{ arg.name }}"
type="file"
[(ngModel)]="moduleArguments[arg.name]"
(change)="onFileChange($event)"
>
<p>{{ arg.description }}<p>
</fieldset>
</li>
</ul>
</modal-body>
<modal-footer>
<button type="button" class="btn btn-default" data-dismiss="modal" (click)="modal.dismiss()">Dismiss</button>
<button type="submit" class="btn btn-primary">Run</button>
</modal-footer>
</form>
</modal>
In onChange, fileArgument.name is set to the value of event.originalTarget.id - the id of an actual HTML element in the page
And chrome error is saying:
Failed to set the 'value' property on 'HTMLInputElement'
Edit since you added the html - you have bound the 'moduleArguements' property to the file input element's ngmodel - as a result, changing that value will cause angular to try and modify the value property on the file input which is not permitted.
What is the purpose of updating that value? Is it just to feedback to the user?
If you remove the ngModel binding from the input element it should work - you are using the onFileChange event to capture the new filename anyway (although in the controller it is just onChange?)
Short Answer: You cannot cannot actually change the value of the this.moduleArguments[this.fileArgument.name]as it would be a security issue.
Explaination: You would be changing the actual value of this.fileArgument.name to something else, which would allow other people with ill intent to do the same. Essentially, an attacker could change that name to redirect any attempts to use that file to another file. So, Java (or Flash, or any other programming language) could not programmatically change that due to security reasons.
With your work-around, you are not actually setting a File data member, therefore JS does not see this as a security hazard.
Remember, [almost] anything you can do involving website/server code (or code interacting with either), an attacker can do as well. That's why JS, in this case, blocks people from changing the content of this specific standard File object. Hope this helps!
have you tried this:
var fileName = this.fileArgument.name;
this.moduleArguments[fileName] = response.filename;
and also if you are somewhere in JS changing the 'value' of your
tag, you will get that error, please refer to:
https://stackoverflow.com/a/29721020/2071008

Best (default) approach for aurelia databinding/gui element initialization

I'm playing around with Aurelia.js in combination with semantic-ui.
Both framework have abilities to fill for instance "select" elements of html.
(Following the 2 "offical examples".)
The way of semantic would for instance be:
(<any>$('#semanticSelect'))
.dropdown({
apiSettings: {
url: '//api.semantic-ui.com/tags/{query}'
}
})
;
The way of Aurelia according to the sample would be with httpclient
users = [];
constructor(private http: HttpClient) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
and in html with
repeat.for="user of users"
and binding according to needs like
<div class="item" repeat.for="user of users" data-value.bind="user.id">${user.login}</div>
So now I'm a little bit confused what's the correct way to work with? Can anyone explain me what's the difference and what's the recommed way?
It's a primary question how GUI controls should be initialized -> by aurelia framwork methods or of semantic ui framework! I'm considering about performance, caching and safety. I have nowhere read how it's recommed to do.
Thanks!
If you are going to use a full framework like Aurelia, I would suggest to just use the Aurelia method since semantic-ui is only for view layout and css with some javascript components.

How to restrict a route to an admin user in Meteor?

as an iOS developer primarily, I'm very new to webdev. I'm looking into Meteor and have some questions regarding routing -- my apologies if they are very easy.
I am using the Meteor Router package to create routes, but I would like to have some pages only accessible to the admin user.
Meteor.Router.add({
'/' : 'home',
'/admin' : 'admin'
});
So I have a simple route setup as above, but I'm not sure how to restrict access to the /admin route.
Is it as simple as something like this? What would be a good way to restrict the route to the /admin page and show a warning or perhaps even redirect them back to the / page?
Thank you!
client.html
<head>
<title>My App</title>
</head>
<body>
{{renderPage}}
</body>
<template name="home">
{{greeting}}
</template>
<template name="admin">
{{greeting}}
</template>
client.js
Template.admin.greeting = function () {
var currentUser = Meteor.user();
if (null !== currentUser && 'admin' === currentUser.username) {
return "Hello Admin!";
}
else{
return "Sorry, only admins can see this page";
}
};
The best way to restrict access to a route is with the router itself (rather than pushing the problem to your controller). You have a couple of choices in how you do this:
Routing Function
You could make the /admin route look like:
'/admin': function() {
return {
as: 'admin',
to: function() {
if (Meteor.user() && Meteor.user().username === 'admin') {
return 'admin';
} else {
return 'unauthorized';
}
}
};
}
I'm assuming you have an unauthorized template that renders a 403 page or something informative.
Filter
Alternatively, you can leave your original /admin route as it was and add a filter:
Meteor.Router.filters({
'needsAdmin': function(page) {
if (Meteor.user() && Meteor.user().username === 'admin') {
return page;
} else {
return 'unauthorized';
}
}
});
and use it like so:
Meteor.Router.filter('needsAdmin', {only: 'admin'});
Personally, I like the filter option because it's reusable and it's a little more obvious what's going on.
Another solution is to use Roles package and make sure the user has the 'admin' role before serving data.
$ mrt add roles
Then you can check for roles like with a nice syntax:
if(!Roles.userIsInRole(Meteor.user(), ['admin'])) {
// Redirect...
}
Roles is integrated with the Meteor accounts system and plays nicely with most of the accounts packages.
If you are looking to manage accounts (create/delete Roles and add/remove Roles from a given user) I've created the package Accounts Admin UI. The README has a quickstart and some some notes on how to integrate this with other routing packages.
$ mrt add accounts-admin-ui-bootstrap-3
Use the and parameter:
Meteor.Router.add({
'/admin': { to: 'admin', and: function() {
if (!Meteor.user() || Meteor.user().name != 'admin'){
Meteor.Router.to('/');
}
}}
});
Everyone here has made great points on how to protect an admin panel on the router level. Another possibility is to skip the router all together. I've recently done this with Meteor Candy, a drop-in admin package for Meteor.
The idea is, you could create a Reactive-Dict to hold the state of the admin interface. If you place it into a package, you can ensure that it never collides with your application code. And with the new Dynamic Imports feature, you can virtually keep it off the client until its needed.
Here's how that might work:
<template name="adminPanel">
{{#if show}}
{{> adminPanelUI}}
{{/if}}
</template>
AdminUI = new ReactiveDict();
Meteor.defer(function () {
Blaze.render(Template.MeteorCandy, document.body);
});
Template.adminPanel.helpers({
show: function () {
if (AdminUI.get('show')) {
return true;
}
}
})
On top of that, all you would need is to define the occasion that sets "show" to a truth-y value.

Resources