Skip to content

Commit

Permalink
Authentication guidance. (Avanade#30)
Browse files Browse the repository at this point in the history
* Added a new AuthenticationException. Created Authentication.md documentation to detail how to configure for Azure AD B2C

* Finished Authentication guidance. Add UserId to ExecutionContext and db SessionContext.
  • Loading branch information
chullybun authored Apr 18, 2020
1 parent a5190eb commit 2b80285
Show file tree
Hide file tree
Showing 43 changed files with 475 additions and 81 deletions.
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,29 @@ The key industralisation goals are:

<br/>

## Rapid enterprise-grade API development

As a result of the _Beef_ [Architecture](#Architecture), supporting [Framework](#Framework) and included [Code Generation](#Code-generation) capabilities, enterprise-grade APIs can be developed in a matter of hours, not days, in a standardised and consistent manner.

The APIs created will have the following capabilities out-of-the-box with limited developer effort, so the developer can focus on the key business value:
- Rich [Entity](./docs/Layer-Entity.md) (DTO) functionality including [INotifyPropertyChanged](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.inotifypropertychanged), [IEditableObject](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.ieditableobject), [ICloneable](./src/Beef.Core/Entities/ICloneable.cs), [ICopyFrom](./src/Beef.Core/Entities/ICopyFrom.cs), [ICleanUp](./src/Beef.Core/Entities/ICleanUp.cs), [IUniueKey](./src/Beef.Core/Entities/IUniqueKey.cs), etc.
- Rich [Reference data](./docs/Reference-Data.md) capabilities, including caching, optimised serialisation, and enriched API endpoints.
- Rich [Validation](./docs/Beef-Validation.md) capability to simplify and ensure data integrity and consistency.
- CRUD (Create, Read, Update and Delete) for Database ([Stored procedures](./src/Beef.Data.Database/README.md) and [Entity Framework](./src/Beef.Data.EntityFrameworkCore/README.md)), [Cosmos DB](./src/Beef.Data.Cosmos/README.md) and [OData](./src/Beef.Data.OData/README.md) in a standardised manner.
- An approach and tooling to automate and manage [database](./tools/Beef.Database.Core/README.md) set up, configuration, and deployment.
- [Paging](./src/Beef.Core/Entities/PagingArgs.cs) (skip and top) and resulting total count, that flows from API through to the underlying data source in a consistent and seamless manner.
- [ETag](https://en.wikipedia.org/wiki/HTTP_ETag) (concurrency) and `If-Match`/`If-None-Match` handling.
- JSON response field [filtering (include/exclude)](./src/Beef.Core/Json/JsonPropertyFilter.cs) to minimise resulting payload size (e.g. `$fields=firstname,lastname`)
- [HTTP Patch](./docs/Http-Patch.md) support, where required, in a simplified and consistent manner.
- An end-to-end intra-domain [integration testing](./tools/Beef.Test.NUnit/README.md) approach enables effective tests to be built easily and quickly.
- [Event](./src/Beef.Events/README.md) publishing and subcribing to enable an event-driven architecture.

To implement these included capabilities would literally take months/years to build and test; these are available for developers to use immediately, and contribute back if so inclined.

To [get started](#Getting-started) a .NET Core [template capability](./templates/Beef.Template.Solution/README.md) is provided to enable you to get a solution up and running in minutes.

<br/>

## Architecture

_Beef_ has been developed to encourage the standardisation and industrialisation of the tiering and layering within the microservices (APIs) of an Application Architecture.
Expand Down Expand Up @@ -140,15 +163,12 @@ Sample | Description

The following are references to additional documentation (these are all accessible via links within this and other documentation):

### Major versions

- [v3.1](./docs/Upgrade-dotnet-core-v3-1.md)

### General

- [Reference data](./docs/Reference-Data.md)
- [Validation](./docs/Beef-Validation.md)
- [HTTP PATCH](./docs/Http-Patch.md)
- [Authentication](./docs/Authentication.md)

### Solution

Expand Down Expand Up @@ -180,6 +200,10 @@ The following are references to additional documentation (these are all accessib
- [OrderBy element](./docs/Table-OrderBy-element.md)
- [Execute element](./docs/Table-Execute-element.md)

### Major versions

- [v3.1](./docs/Upgrade-dotnet-core-v3-1.md)

<br/>

## License
Expand All @@ -190,7 +214,7 @@ _Beef_ is open source under the [MIT license](./LICENSE) and is free for commerc

## Getting started

To start using _Beef_ you do not need to clone the repo; you just need to create a solution with the underlying projects using the prescribed [solution structure](./docs/Solution-Structure.md), including referencing the appropriate NuGet packages. To accelerate this a .NET Core [template capability](./templates/Beef.Template.Solution/README.md) is provided to enable you to get up and running in minutes.
To start using _Beef_ you do not need to clone or fork the repo; you just need to create a solution with the underlying projects using the prescribed [solution structure](./docs/Solution-Structure.md), including referencing the appropriate [NuGet packages](#Framework). To accelerate this a .NET Core [template capability](./templates/Beef.Template.Solution/README.md) is provided to enable you to get up and running in minutes.

See the following for example end-to-end usage; each demonstrating the same API functionality leveraging different data sources to accomplish:
- [Cosmos sample](./docs/Sample-Cosmos-GettingStarted.md)
Expand Down
223 changes: 223 additions & 0 deletions docs/Authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Authentication

This article will describe how to integrate _authentication_ into a _Beef_ solution; in which _Beef_ in an of itself does not enable directly, but leverages the capabilities such as [Azure Active Directory B2C](https://azure.microsoft.com/en-us/services/active-directory-b2c/) to perform. Equally, it could be any _Identity platform_ of your choosing.

For the purposes of this artice _AAD B2C_ will be used. Review Microsoft's [documentation](https://docs.microsoft.com/en-us/azure/active-directory-b2c/) on how to set up and configure in [_Azure_](https://portal.azure.com/) as this will not be covered here.

<br/>

## Company.AppName.Api

The _authentication_ process primarily takes place within the API itself. This capability is added within the `Startup.cs` leveraging the standard _ASP.NET Core_ [authentication](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication/) capabilities, as further described.

<br/>

### ConfigureServices

Within the `ConfigureServices` method a call to `AddAuthentication` is required to configure. For _AAD B2C_ `AddAzureADB2CBearer` is used to load in the appropriate configuration ([`Microsoft.AspNetCore.Authentication.AzureADB2C.UI`](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.AzureADB2C.UI) NuGet package is required).

``` csharp
public void ConfigureServices(IServiceCollection services)
{
// Add authentication using Azure AD B2C.
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => _config.Bind("AzureAdB2C", options));

...
```

<br/>

The `Bind` method loads the configuration from the application settings; for _Beef_ this is the `webapisettings.json` file. The `"AzureAdB2C"` represents the node within the underlying JSON that contains the corresponding configuration:

``` json
{
"AzureAdB2C": {
"Domain": "Xxxx.onmicrosoft.com", // Azure AD B2C domain name
"Instance": "https://Xxxx.b2clogin.com/tfp/", // Instance name, the domain name Xxxx is duplicated here
"ClientId": "12345678-097e-4786-b489-123dabeff688", // Application (client) identifier
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn" // SignUpSignIn policy name
}, ...
```

<br/>

#### Swagger

Where [Swagger UI](https://www.nuget.org/packages/Swashbuckle.AspNetCore.SwaggerUI/) is being used and support for entering in the bearer token is required, then `AddSecurityDefinition` and `OperationFilter` are required, as follows:
``` csharp
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Xxxx API", Version = "v1" });

var xmlName = $"{Assembly.GetEntryAssembly()!.GetName().Name}.xml";
var xmlFile = Path.Combine(AppContext.BaseDirectory, xmlName);
if (File.Exists(xmlFile))
c.IncludeXmlComments(xmlFile);

c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});

c.OperationFilter<SecurityRequirementsOperationFilter>();
});
```

<br/>

### Configure

The next step is to add the _authentication_ into the HTTP request pipeline. The sequencing of this is important, as follows: `UseRouting`, `UseAuthentication`, `UseAuthorization`, `UseExecutionContext` and `UseEndpoints`.

For _Beef_ the [`ExecutionContext`](../src/Beef.Core/ExecutionContext.cs) plays a key role for housing the user details, namely the `Username` (optionally `UserId`). For the likes of _authorization_ `SetRoles` can also be used. To enable additional capabilities a custom [`ExecutionContext`](../samples/Cdr.Banking/Cdr.Banking.Business/ExecutionContext.cs) can be created (inheriting base) similar to that demonstrated within the [Cdr.Banking](../samples/Cdr.Banking/README.md) sample.

The `UseExecutionContext` also represents an opportuntity to perform further authentication validation, such as verifying the issuer (`iss`) and audience (`aud`) claims for example.

_Note:_ the `Username` is set to `emails#oid` claims as the `emails` value may not be unique and is mutable, whereas the `oid` is unique and immutable. This may also be appropriate in your scenario especially where the `Username` is used for the likes of auditing.

``` csharp
public void Configure(IApplicationBuilder app, IHttpClientFactory clientFactory)
{
...

// Use routing, authentication and authorization.
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

// Add execution context set up to the pipeline (must be after UseAuth* as needs claims from user).
app.UseExecutionContext((ctx, ec) =>
{
if (ctx.User.Identity.IsAuthenticated)
{
if (Guid.TryParse(ctx.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value, out var oid))
fec.UserId = oid;
else
throw new Beef.AuthenticationException("Token must have an 'oid' (object identifier GUID) claim.");

fec.Username = $"{ctx.User.FindFirst("emails")?.Value ?? throw new Beef.AuthenticationException("Token must have an 'emails' claim.")}#{fec.UserId}";
}
else
fec.Username = "Anonymous";
});

// Finally add the controllers.
app.UseEndpoints(endpoints => endpoints.MapControllers());
```

</br>

_Disclaimer:_ the example above is for illustrative purposes only; it is the responsibility of the developer to fully implement the claims verification that is applicable to their specific use case.

</br>

## Company.AppName.CodeGen

For the authentication to occur within an API invocation the [`AuthorizeAttribute`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authorization.authorizeattribute) must be specified. The output of this _attribute_ is controlled by the [code generation](../tools/Beef.CodeGen.Core/README.md) configuration.
The following XML elements support the `WebApiAuthorize` attribute, with two options `Authorize` or `AllowAnonymous`. The value is inherited from its parent within the hierarchy where not explicitly defined (overridden):

1. [`CodeGeneration`](./Entity-CodeGeneration-element.md)
2. [`Entity`](./Entity-CodeGeneration-element.md)
3. [`Operation`](./Entity-CodeGeneration-element.md)

_Note:_ Where no `WebApiAuthorize` attribute is specified and cannot be inferred via parental inheritence, it will default to `AllowAnonymous`.

<br/>

## Company.AppName.Test

To support the [intra-domain integration testing](../tools/Beef.Test.NUnit/README.md) the _bearer token_ must be passed from the test to the API otherwise all requests will fail with an authentication error.


### FixtureSetUp

The [`AgentTester`](../tools/Beef.Test.NUnit/AgentTester.cs) has a static `RegisterBeforeRequest` method that enables the [`HttpRequestMessage`](https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httprequestmessage) to be modified prior to the request being made.
Refer to the Microsoft [documentation](https://docs.microsoft.com/en-us/azure/active-directory-b2c/configure-ropc) for further AAD B2C configuration and troubleshooting.
The following code demonstrates the creation of the _bearer token_ by calling the _OAuth_ endpoint passing the username and password. The resulting _token_ is then added to the HTTP request header. Note that the username comes from the `ExecutionContext.Current.Username` which is set within each executing test (see next section).

``` csharp
[SetUpFixture]
public class FixtureSetUp
{
private static readonly KeyedLock<string> _lock = new KeyedLock<string>();
private static readonly Dictionary<string, string> _userTokens = new Dictionary<string, string>();

[OneTimeSetUp]
public void OneTimeSetUp()
{
TestSetUp.RegisterSetUp(async (count, _) =>
{
return await DatabaseExecutor.RunAsync(
count == 0 ? DatabaseExecutorCommand.ResetAndDatabase : DatabaseExecutorCommand.ResetAndData,
AgentTester.Configuration["ConnectionStrings:Database"],
typeof(DatabaseExecutor).Assembly, typeof(Database.Program).Assembly, Assembly.GetExecutingAssembly()).ConfigureAwait(false) == 0;
});

AgentTester.StartupTestServer<Startup>(environmentVariablesPrefix: "AppName_");
AgentTester.DefaultExpectNoEvents = true;
AgentTester.RegisterBeforeRequest(BeforeRequet);
}

private static void BeforeRequet(HttpRequestMessage r)
{
var username = ExecutionContext.Current.Username;
if (username.Equals("Anonymous", System.StringComparison.OrdinalIgnoreCase))
return;

// Cache the token for a user to minimise web calls (perf improvement).
_lock.Lock(username, () =>
{
if (!_userTokens.TryGetValue(username, out string? token))
{
var data = new NameValueCollection
{
{ "grant_type", "password" },
{ "client_id", "12345678-097e-4786-b489-123dabeff688" }, // Application (client) identifier
{ "scope", $"openid 12345678-097e-4786-b489-123dabeff688 offline_access" },
{ "username", $"{username}@domain.com" }, // Appends domain to user (if applicable)
{ "password", "password" } // Assumes all test users have same password
};

// The 'Xxxx' represents your AAD B2C domain
using var webClient = new WebClient();
var bytes = webClient.UploadValues("https://Xxxx.b2clogin.com/Xxxx.onmicrosoft.com/oauth2/v2.0/token?p=B2C_1_ROPC_Auth", "POST", data);
var body = Encoding.UTF8.GetString(bytes);
token = (string)JObject.Parse(body)["access_token"]!;
_userTokens.Add(username, token);
}

r.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
});
}
}
```

<br/>

### Tests

The username can be specified for each test, either using the [`TestSetUpAttribute`](../tools/Beef.Test.NUnit/TestSetUpAttribute.cs) or specifiying when invoking the [AgentTester.Create](../tools/Beef.Test.NUnit/AgentTester.cs). This in turn will set the `ExecutionContext.Current.Username`.

``` csharp
[Test, TestSetUp("username")]
public void A110_GetMe_NotFound()
{
AgentTester.Create<ClaimantAgent, Claimant>()
.ExpectStatusCode(HttpStatusCode.NotFound)
.ExpectErrorType(Beef.ErrorType.NotFoundError)
.Run((a) => a.Agent.GetMeAsync());

AgentTester.Create<ClaimantAgent, Claimant>("username2")
.ExpectStatusCode(HttpStatusCode.NotFound)
.ExpectErrorType(Beef.ErrorType.NotFoundError)
.Run((a) => a.Agent.GetMeAsync());
}
```
4 changes: 2 additions & 2 deletions docs/Layer-Entity.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Entity (DTO)

The *Entity* ([DTO](https://en.wikipedia.org/wiki/Data_transfer_object)) is primarily responsible for defining the domain-based data model (and contract where accessible externally over-the-wire). They are also used to define the [reference data](./Reference-Data.md) model.
The *Entity* ([DTO](https://en.wikipedia.org/wiki/Data_transfer_object)) is primarily responsible for defining the domain-based model (and contract where accessible externally over-the-wire). They are also used to define the [reference data](./Reference-Data.md) model.

The aim here is decouple this definition, where applicable, from the underlying data source. This is likely to be defined differently, with an alternate naming conversion, alternate shape/structure, etc. This also enables the data source to evolve independently of this model, as well as possibly hide additional implementaion details.
The aim here is decouple this definition, where applicable, from the underlying data source. This is likely to be defined differently, with an alternate naming convention, alternate shape/structure, etc. This also enables the data source to evolve independently of this model, as well as possibly hide additional implementaion details.

<br/>

Expand Down
Loading

0 comments on commit 2b80285

Please sign in to comment.