简体   繁体   中英

How to secure endpoints on api resource based on client in identityserver

I have my own authorization server built on top identityserver4 where I want to secure all apis on a host. System is just a simple mimic of google developers or facebook developers where application owners sign up and get client id and client secrets for access grant on apis.

So I followed client_credentials flow on identityserver4 samples. All working fine. I built a public UI for app owners to create apps and choose which apis to access from their apps. I make use of IConfigurationDbContext for CRUD procecces on internal tables of identityserver.

The problem is I couldn't find a way to secure apis based on app owners' choices, when a developer crate an app and choose a few logical endpoints to access, they still can reach all enpoints. What I have done is as follows;

Authorization Server Startup

services.AddIdentityServer()
              .AddDeveloperSigningCredential()
              .AddInMemoryCaching()
              .AddOperationalStore(storeOpitons =>
              {
    storeOpitons.ConfigureDbContext = builder =>
      builder.UseSqlServer(Configuration.GetConnectionString("Default"),
      sql => sql.MigrationsAssembly(migrationsAssembly));
})
              .AddConfigurationStore(storeOptions =>
              {
    storeOptions.ConfigureDbContext = builder =>
      builder.UseSqlServer(Configuration.GetConnectionString("Default"),
      sql => sql.MigrationsAssembly(migrationsAssembly));
});

Saving Client Method

public IActionResult SaveApp(ClientViewModel model, List<SelectedApi> selectedApis)
{
    //ommited for brevity

    Client client = new Client
    {
        Description = model.Description,
        ClientName = model.Name,
        RedirectUris = new[] { model.CallBackUri }
    };
    client.AllowedScopes = selectedApis.Where(a => a.apiValue == "true").Select(a => a.apiName).ToList();
    //e.g : client.AllowedScopes = {"employee_api"};

    _isRepository.SaveClient(client, userApp);

}

Api Project Startup

services.AddAuthentication("Bearer").AddJwtBearer(opt => {
    opt.Authority = "http://localhost:5000";
    opt.Audience = "employee_api";
    opt.RequireHttpsMetadata = false;

});

Api Sample Controller

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
    private readonly IEmployeeRepository _employeeRepoistory;

    public EmployeeController(IServiceProvider provider, IEmployeeRepository employeeRepository) : base(provider)
    {
        _employeeRepoistory = employeeRepository;

    }
    [HttpGet]
    public IActionResult GetEmployees([FromQuery] EmployeeResourceParameter parameter)
    {
        return Ok(_mapper.Map<IEnumerable<EmployeeModel>>(_employeeRepoistory.GetAll(parameter)));
    }
    [HttpGet("{id:int}")]
    public IActionResult GetEmployeeById(int id)
    {
        var emp = _employeeRepoistory.GetById(id);

        return Ok(_mapper.Map<EmployeeModel>(emp));

    }
}

What I want is if a developer choose employee_api, they should just reach to EmployeeController's endpoints. However right now, they can reach all the apis no matter of what their choices are. What are the steps to take for this on api side or auth server side?

在此处输入图像描述

Finally I get it done.. First of all, I realized that it is important to grasp the relation between ApiResource -> Scopes , Clients -> AllowedScopes . I suggest you to read the parts about them in the docs and here

When a client is registered to identityserver and then choose the api endpoints ( eg: organization, employee, calender ) they should be registered as allowedScopes of client ( they live in ClientScopes table ), I was doing it in right way. What I was doing wrong is I suppose all these scopes are ApiResources ( for my case, because all my apis are living in the same host which I call it as CommonServiceApi, just one web api app ). I redefined my ApiResources and its Scopes, as follows;

new ApiResource("commonserviceapi", "Common Service API")
{
    Scopes = {
        new Scope("calender_api", "Calender Api"),
        new Scope("employee_api", "Employee Api"),
        new Scope("organization_api", "Organization Api"),
    }
}

On the api side, endpoints should be authorized with policies as indicated here . Within the access token, allowed scopes of the requesting client are passed to the api app, so api grants accesses according to these values.

So Api Startup

services.AddAuthentication("Bearer").AddJwtBearer(opt =>
            {
    opt.Authority = "http://localhost:5000";
    opt.Audience = "commonserviceapi";

    opt.RequireHttpsMetadata = false;

});

services.AddAuthorization(options =>
{
    options.AddPolicy("ApiEmployee", builder =>
    {
        builder.RequireScope("employee_api");
    });
    options.AddPolicy("ApiOrganization", builder =>
    {
        builder.RequireScope("organization_api");
    });
});

And Api Controllers

[Authorize(Policy = "ApiEmployee")]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
    ...

RequireScope is an extension method of IdentityServer4.AccessTokenValidation package by the way. You should include this package to your api project.

And lastly, this was a confusing point for me; while requesting an access token from server, scope parameter should be empty , as identityserver takes it from client's allowdScopes values. Almost all samples were filling this field, so you'd think it should be filled.

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