Facebook OAuth 2.0 provider in ASP.NET Core 5 Web API.

5 minute read

ASP.NET Core 5’s documentation is not clear about how to use external OAuth 2.0 provider for the Web API.

This post talks about how to use Facebook as the external OAuth 2.0 provider for the Web API. This is based on this commit in my GitHub repository. The assumption is that you already have a working Web API with JWT bearer authentication, and you only want to add the external OAuth 2.0 provider to your Web API.

Configuring Facebook OAuth 2.0 provider and storing the secrets

First, follow the ASP.NET Core 5’s documentation to get the Facebook App ID and the Facebook App secret, and to store the App ID and the App secret using Secret Manager. (For production usage, you need to use other approaches to store the secrets like App ID and App secret. This is beyond the scope of this post.) When you set up at the Facebook side, remember to configure the “https://our_hostname/signin-facebook” as the redirection URI. This will be used by ASP.NET Core.

Enabling Facebook at the ASP.NET Core Web API

Add Microsoft.AspNetCore.Authentication.Facebook package to your project. You need to specify the correct version since the latest version at this time is only for ASP.NET Core 6.

At the CreateHostBuilder method in Program.cs, since CreateDefaultBuilder is used, the user secrets configuration source is automatically added in Development mode.

At Startup.ConfigureServices, use services.AddAuthentication().AddFacebook(), as shown in the following sample codes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
services.AddAuthentication()
    .AddCookie(options =>
        {
            // Your original codes.
        })
    .AddJwtBearer(x =>
        {
            // Your original codes.
        })
    .AddFacebook(facebookOptions =>
        {
            // This is assuming that we use the secrete storage to store the secrets.
            facebookOptions.AppId = Configuration["Authentication:Facebook:AppId"];
            facebookOptions.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
            facebookOptions.SaveTokens = true;
        });

Implementing the authentication API

We need to create the API to sign in via the Facebook OAuth 2.0 provider. In my GitHub repository codes, it is the SignInFacebookAsync method of the UsersController class. To explain this method better, the ordered authentication flow is described below.

Authentication flow among the front-end, ASP.NET Core, and Facebook.
Authentication flow among the front-end, ASP.NET Core, and Facebook.
  1. The front-end application sends the target API request. In our codes, it is “GET /api/Users/authenticate/facebook” (handled by SignInFacebookAsync method).
  2. Upon receiving the request, SignInFacebookAsync method goes to the “challenge” flow. ASP.NET Core’s internal FacebookHandler does the real “challenge” flow to prepare for the redirection information. The redirection information includes the target provider’s AuthorizationEndpoint path, the client-id, and the redirection_uri, etc. ASP.NET Core internally uses “/signin-facebook” as the redirection_uri. We do not need to change this. We only need to configure this path at Facebook OAuth 2.0 provider side. Then Redirection (302) is returned to the front-end application.
  3. The front-end application gets Redirection (302) and redirects to the target Facebook URL to perform the authentication at Facebook.
  4. When Facebook finishes the authentication, it responds with another Redirection (302) to the front-end application. This redirection location is “https://our_hostname/signin-facebook”.
  5. The front-end application gets Redirection (302) and redirects to our ASP.NET Core Web API. ASP.NET Web API internally will process this “https://our_hostname/signin-facebook” request, by working with Facebook OAuth 2.0 provider.
  6. After the above is done, ASP.NET Core internally responds with yet another Redirection (302) to the front-end. This time, the redirection location is the original API URL. In our codes, it is “GET /api/Users/authenticate/facebook” (handled by SignInFacebookAsync method).
  7. The front-end application gets Redirection (302) and redirects to the API URL.
  8. Upon receiving the request, SignInFacebookAsync method goes to the “authenticate” flow. And eventually returns with a JWT.
  9. Other APIs can use the JWT for authentication. At the end of this post, we will discuss another approach, using the Facebook authentication scheme directly.

Watch out! If the front-end is the Swagger UI on a browser, redirecting to www.facebook.com will fail due to CORS policy. In this case, use browser’s develop tool to check the target redirection URL and go to the target Facebook URL manually.

The code block of SignInFacebookAsync method is the following.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// This is the "GET api/Users/authenticate/facebook API"
[AllowAnonymous]
[HttpGet("authenticate/facebook")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]
[ProducesResponseType(StatusCodes.Status302Found)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<IActionResult> SignInFacebookAsync()
{
    var authScheme = FacebookDefaults.AuthenticationScheme;

    // Try to authenticate.
    var authResult = await Request.HttpContext.AuthenticateAsync(authScheme);
    if (!authResult.Succeeded
        || authResult?.Principal == null
        || !authResult.Principal.Identities.Any(id => id.IsAuthenticated)
        || string.IsNullOrEmpty(authResult.Properties.GetTokenValue("access_token")))
    {
        // Challenge the Facebook OAuth 2.0 provider.
        await Request.HttpContext.ChallengeAsync(authScheme, new AuthenticationProperties
        {
            AllowRefresh = true,
            ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
            IssuedUtc = DateTimeOffset.UtcNow,
            // We provide this API's own path here so that the final redirection can go
            // to this method.
            RedirectUri = Url.Action("SignInFacebookAsync")
        });

        // Get the location response header.
        if (Response.Headers.TryGetValue("location", out var locationResponseHeader))
        {
            return Redirect(locationResponseHeader);
        }
        return Unauthorized();
    }

    // Then you need to get the role and user ID based on the user name.
    // In real world scenario, you may have other flows to add the target user from external
    // OAuth 2.0 provider into ASP Identity.
    // This is beyond the scope of this sample codes.
    // For tutorial purpose here, we will use fixed Admin role.

    // In the API design of this sample codes, the APIs will only accept JWT Bearer authentication
    // or Cookies authentication.
    // Therefore, we need to get JWT or create the auth cookie.
    // For tutorial purpose, we use JWT.

    // In real world scenario, you may want to use auth.Properties.ExpiresUtc to set your JWT expiration
    // or auth cookie expiration accordingly.

    var token = _jwtAuth.GetToken(claimsIdentity.Name, new List<string> { UserRole.RoleAdmin });
    return Ok(token);
}

Discussion about using Facebook authentication scheme directly

In our sample codes, we choose to use JWT authentication for our APIs. Different design can be used here. For example, you can configure API to use Facebook authentication scheme directly (instead of JWT or cookie) with [Authorize(AuthenticationSchemes = FacebookDefaults.AuthenticationScheme)]. In this case, once the authentication via Facebook is done, the API is authenticated. If you choose to do so, consider the following.

  1. If other claims, e.g., the role claim, need to be added, check this ASP.NET post.
  2. When you send an API request using Facebook authentication scheme, if the authentication is not done yet, Redirection (302) will be returned, instead of Unauthorized (401). You may want to change this behavior to return Unauthorized (401) instead. You probably don’t want to let non-authentication-type of APIs to handle the authentication redirection request.