Server-Side Microsoft Authentication with ASP.NET Core

Cedric Gabrang
5 min readJun 25, 2023

--

In some applications, to access a specific functionality, user has to register and log in. Users can also sign-in to their existing accounts from popular social platforms like Microsoft, to simplify registrations and logins.

There are different specifications involved in enabling external authentication for your app. You need the Client ID (consumer Key/API key) and Client Secret from the provider to complete the authentication flow.

Both the Client ID and Client Secret are needed to confirm your app’s identity and it is critical that you do not expose your Client Secret. Mobile apps are not a great place to store secrets and anything stored in a mobile app’s code, binaries, or otherwise, is considered to be insecure.

The best practice here is to use a web backend as a middle layer between your applications and the authentication provider to ensure better security.

ASP.NET Core Backend

Create a new ASP.NET Core Web API project.

Add Cookie Authentication

Add the authentication middleware services AddAuthentication and AddCookie methods to your Program class:

var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;

services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie();

By setting DefaultScheme to CookieAuthenticationDefaults.AuthenticationScheme, you are indicating that the cookie authentication scheme should be used as the default scheme when an authentication scheme is not explicitly specified.

App Registration in Microsoft Developer Portal

Navigate to the Azure portal — App registrations page and create or sign into a Microsoft account:

  1. Select New registration
  2. Enter a Name
  3. Select an option for Supported account types
  4. Under Redirect URI, select Web then enter your development URL with /signin-microsoft appended
  5. Select Register

Create client secret

  1. In the left pane, select Certificates & secrets.
  2. Under Client secrets, select New client secret.
  3. Add a description for the client secret.
  4. Select the Add button.
  5. Under Client secrets, copy the value of the client secret.

Store the client ID and secret

Store the Application (client) ID that can be found on the Overview page of the App Registration and Client Secret you created on the Certificates & secrets page with Secret Manager.

Initialize your ASP.NET Core project for secret storage. Go to View, then Terminal or press Ctrl + `. Switch to the root project directory, then:

dotnet user-secrets init

Once initialized, you can now store the sensitive settings in the local secret store with the secret keys Authentication:Microsoft:ClientId and Authentication:Microsoft:ClientSecret:

dotnet user-secrets set "Authentication:Microsoft:ClientId" "<client-id>"
dotnet user-secrets set "Authentication:Microsoft:ClientSecret" "<client-secret>"

IMPORTANT: Secret Manager is designed for local development and stores sensitive settings on the development machine’s file system. It’s not suitable for production environments. Instead, when deploying to production, integrate your application with Azure Key Vault to securely retrieve the necessary secrets at runtime.

Configure Microsoft Account Authentication

In your ASP.NET Core project, add the Microsoft.AspNetCore.Authentication.MicrosoftAccount NuGet package:

dotnet add package Microsoft.AspNetCore.Authentication.MicrosoftAccount --version 7.0.5

Once installed, add the Microsoft authentication service to your Program class:

var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;
var configuration = builder.Configuration;

services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddMicrosoftAccount(ms =>
{
ms.ClientId = configuration["Authentication:Microsoft:ClientId"];
ms.ClientSecret = configuration["Authentication:Microsoft:ClientSecret"];
ms.SaveTokens = true;
});

Add a controller

Under the Controllers folder of your ASP.NET Core project:

  • Create a new class called AuthController. Inherit from ControllerBase for common functionality for controllers.
  • Add [ApiController] attribute to indicate that this controller is an API controller.
  • Add [Route("[controller]")] attribute to set the route for this controller. In this case, [controller] is a placeholder that will be replaced with the controller's name, which means the route for this controller would be "/Auth" since the controller's name is "AuthController”.
  • Add a callback scheme which will be used later for the callback URL.
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
const string callbackScheme = "myapp";
}

Authenticate method

  • Within the controller class, create an asynchronous method called Authenticate with the [HttpGet("authenticate")] attribute. This attribute specifies that the method will handle HTTP GET requests to the "authenticate" endpoint.
  • Call the AuthenticateAsync method on the HttpContext instance to authenticate the current request using the Microsoft Account authentication scheme.
[HttpGet("authenticate")]
public async Task Authenticate()
{
var result = await Request.HttpContext.AuthenticateAsync(MicrosoftAccountDefaults.AuthenticationScheme);
}
  • Check the authentication result to determine if the authentication was successful and if the user is authenticated.
  • When the authentication fails, or the user is not authenticated, use ChallengeAsync method.
if (!result.Succeeded || 
result?.Principal == null ||
!result.Principal.Identities.Any(id => id.IsAuthenticated) ||
string.IsNullOrEmpty(result.Properties.GetTokenValue("access_token")))
{
// Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(MicrosoftAccountDefaults.AuthenticationScheme);
}
  • If the authentication is successful, extract the claims from the authenticated user’s principal by accessing the Claims.
  • Look for the name claim by searching for the claim with the type System.Security.Claims.ClaimTypes.GivenName.
if (!result.Succeeded || 
result?.Principal == null ||
!result.Principal.Identities.Any(id => id.IsAuthenticated) ||
string.IsNullOrEmpty(result.Properties.GetTokenValue("access_token")))
{
// Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(MicrosoftAccountDefaults.AuthenticationScheme);
}
else
{
var claims = result.Principal.Identities.FirstOrDefault()?.Claims;
var name = claims?.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.GivenName)?.Value;
}
  • Create a dictionary to hold the parameters that will be sent back to the callback URL.
// Get parameters to send back to the callback
var parameters = new Dictionary<string, string>
{
{ "access_token", result.Properties.GetTokenValue("access_token") ?? string.Empty },
{ "refresh_token", result.Properties.GetTokenValue("refresh_token") ?? string.Empty },
{ "expires_in", (result.Properties.ExpiresUtc?.ToUnixTimeSeconds() ?? -1).ToString() },
{ "name", name ?? string.Empty }
};
  • Build the result URL by concatenating the callback scheme with a fragment identifier (#) and the encoded query string parameters.
  • Finally, redirect the current HTTP response to the constructed URL.
// Build the result url
var url = callbackScheme + "://#" + string.Join("&",
parameters.Where(p=> !string.IsNullOrEmpty(p.Value) && p.Value != "-1")
.Select(p=> $"{WebUtility.UrlEncode(p.Key)}={WebUtility.UrlEncode(p.Value)}"));

// Redirect to final url
Request.HttpContext.Response.Redirect(url);

Let’s try it out!

In your Xamarin.Forms or .NET MAUI app, use WebAuthenticator to initiate browser-based authentication:

var result = await WebAuthenticator.Default.AuthenticateAsync(
new WebAuthenticatorOptions()
{
Url = new Uri("https://yoururlhere/auth/authenticate"),
CallbackUrl = new Uri("myapp://"),
});

string token = result?.AccessToken;
string name = result?.Propeties["name"];

Here’s how it looks like when running:

That’s it! You have now created the endpoint to handle the server-side authentication using the Microsoft Account.

You can find the ASP.NET Core source code here.

Cheers!

--

--

Cedric Gabrang

Senior Developer @XAMConsulting | Xamarin, .NET MAUI, C# Developer | React Native | Twitter: @cedric_gabrang