ASP.NET Core Web Authentication¶
Overview¶
The Dotmim.Sync.Web.Server
package used to expose DMS
through ASP.Net Core Web Api is just a wrapper using the web HttpContext
object to figure out what should be done, internally.
Hint
You will find the auth sample here : Web Authentication Sample
Just as a reminder, the Web Server code looks like this:
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{
private WebServerAgent webServerAgent;
private readonly IWebHostEnvironment env;
// Injected thanks to Dependency Injection
public SyncController(WebServerAgent webServerAgent, IWebHostEnvironment env)
{
this.webServerAgent = webServerAgent;
this.env = env;
}
/// <summary>
/// This POST handler is mandatory to handle all the sync process
/// </summary>
/// <returns></returns>
[HttpPost]
public Task Post()
=> webServerAgent.HandleRequestAsync(this.HttpContext);
/// <summary>
/// This GET handler is optional. It allows you to see the configuration hosted on the server
/// </summary>
[HttpGet]
public async Task Get()
{
if (env.IsDevelopment())
{
await this.HttpContext.WriteHelloAsync(webServerAgent);
}
else
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("<!doctype html>");
stringBuilder.AppendLine("<html>");
stringBuilder.AppendLine("<title>Web Server properties</title>");
stringBuilder.AppendLine("<body>");
stringBuilder.AppendLine(" PRODUCTION MODE. HIDDEN INFO ");
stringBuilder.AppendLine("</body>");
await this.HttpContext.Response.WriteAsync(stringBuilder.ToString());
}
}
}
As you can see, we are completely integrated within the ASP.Net Core architecture. So far, protecting our API is just like protecting any kind of ASP.NET Core Api.
If you want to rely on a strong OAUTH2 / OpenID Connect provider, please read:
Microsoft : Mobile application calling a secure Web Api, using Azure AD
Google : OAUTH2 with Google APIS
Identity Server : Protecting an API using Identity Server
DMS
relies on the ASP.NET Core Web Api architecture. So far, you can secure DMS like you’re securing any kind of exposed Web API:
Configuring the controller
Configuring the identity provider protocol
Calling the controller with an authenticated client, using a bearer token
Note
More information about ASP.Net Core Authentication here : Overview of ASP.NET Core authentication
Server side¶
We are going to use a Bearer token validation on the server side:
Unsecure but easier: Using an hard coded bearer token (Do not use this technic in production)
Secured but relying on an external token provider: Using for example Azure Active Directory Authentication.
Configuration¶
You need to configure your Web API project to be able to secure any controller.
Startup.cs
, you should add authentication services, with JWT Bearer protection.services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{})
Here is a quick sample, without relying on any external cloud identity provider (once again, DON’T do that in production, it’s INSECURE and just here for the sake of explanation)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDistributedMemoryCache();
services.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));
// Adding a default authentication system
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // => remove default claims
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
ValidIssuer = "Dotmim.Sync.Bearer",
ValidAudience = "Dotmim.Sync.Bearer",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("RANDOM_KEY"))
});
// [Required]: Get a connection string to your server data source
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
// [Required] Tables involved in the sync process:
var tables = new string[] {"ProductCategory", "ProductModel", "Product",
"Address", "Customer", "CustomerAddress", "SalesOrderHeader", "SalesOrderDetail" };
// [Required]: Add a SqlSyncProvider acting as the server hub.
services.AddSyncServer<SqlSyncProvider>(connectionString, tables);
}
As an example, if you’re using Azure AD authentication, your code should be more like:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// [Required]: Handling multiple sessions
services.AddDistributedMemoryCache();
services.AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));
// Using Azure AD Authentication
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
// [Required]: Get a connection string to your server data source
var connectionString = Configuration.GetSection("ConnectionStrings")["SqlConnection"];
// [Required] Tables involved in the sync process:
var tables = new string[] {"ProductCategory", "ProductModel", "Product",
"Address", "Customer", "CustomerAddress", "SalesOrderHeader", "SalesOrderDetail" };
// [Required]: Add a SqlSyncProvider acting as the server hub.
services.AddSyncServer<SqlSyncProvider>(connectionString, tables);
}
Note
More on Code Configuration Here.
Finally, do not forget to add the Authentication Middlewares (and Session Middleware) as well:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Securing the controller¶
This part is the most easier one. Yo can choose to secure all the controller, using the [Authorize]
attribute on the class itself, or you can use either [Authorize]
/ [AllowAnonymous]
on each controller methods:
The simplest controller could be written like this, using the [Authorize]
attribute:
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{
...
}
Maybe you’ll need to expose the GET
method to see the server configuration. In that particular case, we can use both [Authorize]
and [AllowAnonymous]
:
[ApiController]
[Route("api/[controller]")]
public class SyncController : ControllerBase
{
private WebServerAgent webServerAgent;
public SyncController(WebServerAgent webServerAgent)
=> this.webServerAgent = webServerAgent;
[HttpPost]
[Authorize]
public async Task Post() => webServerAgent.HandleRequestAsync(this.HttpContext);
[HttpGet]
[AllowAnonymous]
public Task Get() => this.HttpContext.WriteHelloAsync(webServerAgent);
}
And eventually, you can even have more control, using the HttpContext
instance, from within your POST
handler:
[HttpPost]
public async Task Post()
{
// If you are using the [Authorize] attribute you don't need to check
// the User.Identity.IsAuthenticated value
if (!HttpContext.User.Identity.IsAuthenticated)
{
this.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
// using scope and even claims, you can have more grain control on your authenticated user
string scope = (User.FindFirst("http://schemas.microsoft.com/identity/claims/scope"))?.Value;
string user = (User.FindFirst(ClaimTypes.NameIdentifier))?.Value;
if (scope != "access_as_user")
{
this.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
return;
}
await orchestrator.HandleRequestAsync(this.HttpContext);
}
Client side¶
From you mobile / console / desktop application, you just need to send your Bearer Token embedded into your HttpClient headers.
The WebRemoteOrchestrator
object allows you to use your own HttpClient
instance. So far, create an instance and add your bearer token to the DefaultRequestHeaders.Authorization
property.
// Getting a JWT token
// You should get a Jwt Token from an identity provider like Azure, Google, AWS or other.
var token = GenerateJwtToken(...);
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// Adding the HttpClient instance to the web client orchestrator
var serverOrchestrator = new WebRemoteOrchestrator(
"https://localhost:44342/api/sync", client:httpClient);
var clientProvider = new SqlSyncProvider(clientConnectionString);
var agent = new SyncAgent(clientProvider, serverOrchestrator);
var result = await agent.SynchronizeAsync();
Xamaring sample¶
Note
More on mobile token acquisition : Acquire token from mobile application
AcquireTokenSilent()
or AcquireTokenInteractive()
, MSAL returns an access token for the requested scopes.string[] scopes = new string[] {"user.read"};
var app = PublicClientApplicationBuilder.Create(clientId).Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result;
try
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch(MsalUiRequiredException)
{
result = await app.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}