简体   繁体   中英

Implementing custom Authorization logic for Web API on ASP.NET Core 6

I have to implement authorization for my web api using another/external API. So I have to get the JWT from the request and call another API with that token to know whether the user is authorized.

presently my authentication is working, and I am using

IServiceCollection.AddAuthentication().AddJwtBearer() // removed code to set options

in sample above, I have removed code to provide options and setting the TokenValidationParameters. So my auth logic is working as expected.

Now i am looking to implement custom Authorization. In my custom authorization logic i have to make call to another/external API and pass some parameters. The parameters will be different for different action methods in my controller. The external API will return bool (indicating whether authorized or not), I don't have need to maintain any application role/claims in my code.

is using dynamic policy name and string parsing as mentioned in doc the only/recommended option.

So i have to get jwttoken from request and call another API with that token to know if user is authorized or not.

You should try to prevent having to make an an outbound API request for each request your API gets.

It seems like you have an external authentication service which lets your users log in and returns a token of sorts. You need to know how that third party signs their tokens, then obtain some form of (public) key from them.

With this key you can validate whether the token has been signed by the party you trust. For this, you configure the appropriate TokenValidationParameters which you pass to your services.AddAuthentication().AddJwtBearer() so you can let Identity validate their token using their keys.

See:

Ultimately you'd also configure some sort of background job that cycles the keys of the external provider when they do, if they do, which they hopefully do.


As for your updated question: you appear to want to use an external service for authorization , ie who can do what.

You'll have to implement this yourself. Usually this is done using scopes, which are retrieved once, during authentication. These can contain values like "finance" to give access to financial controllers, or "finance:orders:list finance:products".

[RequiredScope("finance:orders", "finance:orders:list")]
public IActionResult Index()
{
    return View();
}

If the API you're talking to does not have a way to retrieve the relevant scopes, claims or permissions during authentication (or once per resource), then you can't, for example, cache the user's roles to your controllers or entities.

You need to realise this will incur extra overhead per API call, as well as your application being down when the authentication/authorization service is down.

If you still want to do this, the most trivial way to do async authorization on a controller would be a policy:

public class AuthorizeWithAuthServiceRequirement : IAuthorizationRequirement
{
    public const string PolicyName = "external-authorization-service";
}

public class AuthorizeWithAuthServiceHandler : AuthorizationHandler<AuthorizeWithAuthServiceRequirement>
{
    private IYourApiService _yourApiService;

    public AuthorizeWithAuthServiceHandler(IYourApiService yourApiService/* your DI here */)
    {
        _yourApiService = yourApiService;
    }
    
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizeWithAuthServiceRequirement requirement)
    {
        var httpContext = context.Resource as HttpContext
            ?? throw new ArgumentException("Can't obtain HttpContext");

        // Get the user or their claims or the ID from the route or something
        var user = httpContext.User;
        var claim = user.FindAll("foo-claim");
        var allClaims = user.Claims;
        var id = httpContext.Request.RouteValues["id"];

        // TODO: response and error handling
        var isUserAuthorized = _yourApiService.IsUserAuthorized(user, id, entity, claim, ...);

        if (!isUserAuthorized)
        {
            context.Fail(/* TODO: reason */);
        }
    }
}

You register this with DI like this:

// Register the handler for dependency injection
services.AddSingleton<IAuthorizationHandler, AuthorizeWithAuthServiceHandler>();

// Register the policy
services.AddAuthorization(options =>
{
    options.AddPolicy(AuthorizeWithAuthServiceRequirement.PolicyName, x => { x.AddRequirements(new AuthorizeWithAuthServiceRequirement()); });
});

And then apply it to a controller or action method:

[Authorize(Policy = AuthorizeWithAuthServiceRequirement.PolicyName)]
public class FooController : Controller
{
}

If you want more fine-grained control like custom attributes with parameters (like [CustomAuthorization(ApiPermission.Foo)] ) per controller or action, or if you want to first load an entity and pass that to the handler, see Ilja in Asp.Net Core: Access custom AuthorizeAttribute property in AuthorizeHandler and their GitHub repository demonstrating three different approaches .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM