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.