Tasks:
Design
Setup in yaml file will be done using security schemes and the first type to support is OAuth2 with client id + client secret as authetication. Custom roles will be used to support access based on rights.
This can be done like shown below:
Definition of the security schema:
components:
securitySchemes:
oAuth2Sample:
x-atc-azure-ad-application-roles:
api.execute.all:
description:Access to all operations
name: everything
api.execute.read:
description: Access to smart charging operations.
name: read
type: oauth2
flows:
clientCredentials:
tokenUrl: 'https://login.microsoftonline.com/7b8ae8aa-f7d3-4bfe-a333-eb138ce54b98/oauth2/v2.0/token'
refreshUrl: ''
scopes:
.default
description: OAuth2 using client id + client secret
Usage in a operation:
paths:
'/items/{id}':
put:
summary: 'Updates an item'
description: 'Updates an item'
operationId: updateItem
responses:
'200':
description: OK
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateItemRequest'
parameters:
- name: id
in: path
description: The id of the order
required: true
schema:
type: string
format: uuid
security:
- oAuth2Sample:
- .default
- x-atc-azure-ad-application-roles:
- api.execute.read (can this be a $ref?)
- api.execute.write
The generated code will affect the controller class(es) and will add an Authorize attribute to each method that is setup with security in its operation definition. The Roles property in the Authorize attribute maps to OAuth2 scopes and these are defined in the securiy scheme.
As the Roles property of the Authorize attribute treats multiple attributes on the same method as AND requirements it will be needed to have only 1 attribute per method to support OR and the roles will have to be comma separated. To make the code a bit nicer a static class with scope definitions are generated for each defined security schema.
Make sure Swagger UI shows the required roles per operation.
Validation:
ATC generator will validate the setup:
- Unused role(s) (warning)
- Undefined role(s) (error)
- Roles not defined in OAuth2 schema (error)
The generated code could look something like this:
namespace Demo.Api.Generated.Security
{
public static class OAuth2ClientCredentials
{
// Single roles
public const string ApiExecuteAll = "api.execute.all";
public const string ApiExecuteRead = "api.execute.read";
// Combined roles
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Better readability for combined scopes.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Better readability for combined scopes.")]
public const string ApiExecuteAll_ApiExecuteRead = ApiExecuteAll + "," + ApiExecuteRead;
}
}
namespace Demo.Api.Generated.Endpoints
{
/// <summary>
/// Endpoint definitions.
/// Area: Items.
/// </summary>
[ApiController]
[Authorize]
[Route("api/v1/items")]
[GeneratedCode("ApiGenerator", "1.0.216.0")]
public class ItemsController : ControllerBase
{
/// <summary>
/// Description: Updates an item.
/// Operation: UpdateItem.
/// Area: Items.
/// </summary>
[HttpPut("{id}")]
[Authorize(Roles = OAuth2ClientCredentials.ApiExecuteAll_ApiExecuteRead)]
[ProducesResponseType(typeof(string), StatusCodes.Status200OK)]
public Task<ActionResult> UpdateItemAsync(UpdateItemParameters parameters, [FromServices] IUpdateItemHandler handler, CancellationToken cancellationToken)
{
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
return InvokeUpdateItemAsync(parameters, handler, cancellationToken);
}
private static async Task<ActionResult> InvokeUpdateItemAsync(UpdateItemParameters parameters, IUpdateItemHandler handler, CancellationToken cancellationToken)
{
return await handler.ExecuteAsync(parameters, cancellationToken);
}
}
}