Published:

.NET 7.0 - Facebook Authentication API Tutorial with Example

Tutorial built with .NET 7.0

In this tutorial we'll show how to build a .NET 7.0 API that supports Facebook Authentication.

Tutorial contents


Example .NET 7 API Overview

The example API supports authenticating with a Facebook access token to identify the user, it is designed to be used with a client app (e.g. Angular, React, Vue, Blazor etc) that has implemented Facebook Login. There is an example Angular client app available below.

A Facebook access token is obtained by the client app when a user completes the Facebook Login process, the client app then sends the access token to the .NET API to authenticate.

Client App + .NET API + Facebook Login

These are the three pieces involved in making the example work:

  • Client App - the front-end app with pages for Facebook Login, view account and edit account.
  • Backend API - the .NET API that supports authentication with a Facebook access token, and provides secure access to data for the front-end app.
  • Facebook - in this scenario Facebook acts as an authentication server (identity provider) to verify user credentials and return an access token to the Client App on success. The FB access token is then used to authenticate with the .NET API, which validates the access token with Facebook and returns its own JWT token for accessing secure routes on the .NET API.

EF Core InMemory database used for testing

To keep the API code as simple as possible, it is configured to use the EF Core InMemory database provider which allows Entity Framework Core to create and connect to an in-memory database rather than you having to install a real db server. This can be easily switched out to a real db provider when you're ready to work with a database such as SQL Server, Oracle, MySQL etc. For instructions on how to connect it to a SQL Server database see .NET 6.0 - Connect to SQL Server with Entity Framework Core, for MySQL see .NET 6.0 - Connect to MySQL Database with Entity Framework Core.

Code on GitHub

The tutorial project is available on GitHub at https://github.com/cornflourblue/dotnet-7-facebook-authentication-api.


Tools required to run the .NET 7.0 Tutorial API Locally

To develop and run .NET 7.0 applications locally, download and install the following:

  • .NET SDK - includes the .NET runtime and command line tools
  • Visual Studio Code - code editor that runs on Windows, Mac and Linux
  • C# extension for Visual Studio Code - adds support to VS Code for developing .NET applications


Run the .NET 7.0 Facebook Auth API Locally

  1. Download or clone the tutorial project code from https://github.com/cornflourblue/dotnet-7-facebook-authentication-api
  2. Start the api by running dotnet run from the command line in the project root folder (where the WebApi.csproj file is located), you should see the message Now listening on: http://localhost:4000.
  3. Follow the instructions below to test the API with an example Angular client app.


Debugging in VS Code

You can start the application in debug mode in VS Code by opening the project root folder in VS Code and pressing F5 or by selecting Debug -> Start Debugging from the top menu. Running in debug mode allows you to attach breakpoints to pause execution and step through the application code. For more info on debugging .NET in VS Code see VS Code + .NET - Debug a .NET Web App in Visual Studio Code.

Before running in production

Before running in production also make sure that you update the Secret property in the appsettings.json file, it is used to sign and verify JWT tokens for authentication, change it to a random string to ensure nobody else can generate a JWT with the same secret and gain unauthorized access to your api. A quick and easy way is join a couple of GUIDs together to make a long random string (e.g. from https://www.guidgenerator.com/).


Connect an Angular App with the .NET Facebook Auth API

For full details about the Angular Facebook Login app see the post Angular 14 - Facebook Authentication Tutorial with Example. But to get up and running quickly just follow the below steps.

  1. Install Node.js and npm from https://nodejs.org
  2. Download or clone the project source code from https://github.com/cornflourblue/angular-14-facebook-login-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Remove or comment out the line below the comment // provider used to create fake backend located in the /src/app/app.module.ts file.
  5. Start the application in SSL (https) mode by running npm run start:ssl from the command line in the project root folder, SSL is required for the Facebook SDK to run properly, this will launch a browser with the URL https://localhost:4200/.
  6. You should see the message Your connection is not private (or something similar in non Chrome browsers), don't worry it's just because the Angular development server runs with a self signed SSL certificate. To open the app click the Advanced button and the link Proceed to localhost, it should be hooked up with the .NET 7 FB Auth API that you already have running.


Authentication Flow with the Angular App, .NET API and Facebook Login

These are the steps that happen when a user logs into the Angular App and .NET API with Facebook:

  1. User clicks "Login with Facebook" and enters their credentials
  2. Facebook verifies credentials and returns an FB access token to the Angular App
  3. Angular App sends the access token to the .NET API to authenticate
  4. .NET API validates the access token with the Facebook API
  5. On first login, the .NET API creates a new account for the user (with their name fetched from the Facebook API)
  6. .NET API returns a new short-lived (15 minute) JWT token to the Angular App
  7. Angular App stores the JWT and uses it to make secure requests to the .NET API (You're logged in!)
  8. Angular App starts a timer to repeat steps 3-8 before the JWT expires to auto refresh the JWT and keep the user logged in

Communication with Facebook

Communication with Facebook in the Angular example is enabled with a Facebook App I created in the Facebook Developer Portal (https://developers.facebook.com/) named "JasonWatmore.com Login Example". Facebook Apps are used to configure which products you want to leverage from Facebook (e.g. Facebook Login). The App ID is passed to the Facebook SDK during initialization (FB.init(...)).

To implement Facebook Login in your own Client App you need to create your own Facebook App at https://developers.facebook.com/ and configure it with the Facebook Login product. For more info see the Facebook Login docs at https://developers.facebook.com/docs/facebook-login/web.


.NET 7.0 Facebook Auth API Project Structure

The .NET Facebook auth tutorial project is organised into the following folders:

Authorization
Contains the classes responsible for implementing custom JWT authentication and authorization in the API.

Controllers
Define the end points / routes for the API, controller action methods are the entry points into the API for client applications via HTTP requests.

Models
Represent request and response models for controller action methods. Request models define the parameters for incoming requests and response models define the data that is returned.

Services
Contain business logic, validation and database access code.

Entities
Represent the application data that is stored in the database.
Entity Framework Core (EF Core) maps relational data from the database to instances of C# entity objects in the application for data management and CRUD operations.

Helpers
Anything that doesn't fit into the above folders.

Click any of the below links to jump down to a description of each file along with its code:

 

.NET Allow Anonymous Attribute

Path: /Authorization/AllowAnonymousAttribute.cs

The custom [AllowAnonymous] attribute is used to allow public access to specific action methods when a controller class is decorated with the [Authorize] attribute. It's used in the accounts controller to allow anonymous access to the Authenticate method.

The logic for allowing public access is located in the custom authorize attribute below, authorization is skipped if the action method is decorated with [AllowAnonymous].

The reason I created a custom AllowAnonymous attribute instead of using the one in the .NET Core framework (Microsoft.AspNetCore.Authorization) is for consistency with the other custom auth classes in the project and to avoid ambiguous reference errors between namespaces.

namespace WebApi.Authorization;

[AttributeUsage(AttributeTargets.Method)]
public class AllowAnonymousAttribute : Attribute
{ }
 

.NET Custom Authorize Attribute

Path: /Authorization/AuthorizeAttribute.cs

The custom [Authorize] attribute is added to controller classes or action methods that require the user to be authenticated.

When a controller class is decorated with the [Authorize] attribute all action methods in the controller are restricted to authorized requests, except for methods decorated with the custom [AllowAnonymous] attribute above.

Authorization is performed by the OnAuthorization method which checks if there is an authenticated account attached to the current request (context.HttpContext.Items["Account"]).

On successful authorization no action is taken and the request is passed through to the controller action method, if authorization fails a 401 Unauthorized response is returned.

namespace WebApi.Authorization;

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WebApi.Entities;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        // skip authorization if action is decorated with [AllowAnonymous] attribute
        var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any();
        if (allowAnonymous)
            return;

        // authorization
        var account = (Account?)context.HttpContext.Items["Account"];
        if (account == null)
        {
            // not logged in or role not authorized
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}
 

.NET Custom JWT Middleware

Path: /Authorization/JwtMiddleware.cs

The custom JWT middleware extracts the JWT token from the request Authorization header (if there is one) and validates it with the jwtUtils.ValidateToken() method. If validation is successful the account id from the token is returned and the authenticated account object is added to the HttpContext.Items collection which makes it accessible to all other classes within the scope of the current request.

If token validation fails (or there is no token) the request is not authenticated (anonymous) and only allowed to access public routes because there isn't an authenticated account object attached to the HTTP context. The authorization code that checks for the account object is located in the custom authorize attribute above. When an anonymous/unauthorized request is sent to a secure route the authorize attribute returns a 401 Unauthorized HTTP response.

namespace WebApi.Authorization;

using Microsoft.Extensions.Options;
using WebApi.Helpers;

public class JwtMiddleware
{
    private readonly RequestDelegate _next;
    private readonly AppSettings _appSettings;

    public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
    {
        _next = next;
        _appSettings = appSettings.Value;
    }

    public async Task Invoke(HttpContext context, DataContext dataContext, IJwtUtils jwtUtils)
    {
        var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
        var accountId = jwtUtils.ValidateJwtToken(token);
        if (accountId != null)
        {
            // attach account to context on successful jwt validation
            context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId.Value);
        }

        await _next(context);
    }
}
 

.NET JWT Utils

Path: /Authorization/JwtUtils.cs

The JWT utils class contains methods for generating and validating JWT tokens.

The GenerateJwtToken() method returns a short lived JWT token that expires after 15 minutes, it contains the id of the specified account as the "id" claim, meaning the token payload will contain the property "id": <accountId> (e.g. "id": 1). The token is created with the JwtSecurityTokenHandler class and digitally signed using the secret key stored in the app settings file.

The ValidateJwtToken() method attempts to validate the provided JWT token and return the account id ("id") from the token claims. If validation fails null is returned.

namespace WebApi.Authorization;

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using WebApi.Entities;
using WebApi.Helpers;

public interface IJwtUtils
{
    public string GenerateJwtToken(Account account);
    public int? ValidateJwtToken(string? token);
}

public class JwtUtils : IJwtUtils
{
    private readonly DataContext _context;
    private readonly AppSettings _appSettings;

    public JwtUtils(
        DataContext context,
        IOptions<AppSettings> appSettings)
    {
        _context = context;
        _appSettings = appSettings.Value;

        if (string.IsNullOrEmpty(_appSettings.Secret))
            throw new AppException("JWT secret not configured");
    }

    public string GenerateJwtToken(Account account)
    {
        // generate token that is valid for 15 minutes
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_appSettings.Secret!);
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[] { new Claim("id", account.Id.ToString()) }),
            Expires = DateTime.UtcNow.AddMinutes(15),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }

    public int? ValidateJwtToken(string? token)
    {
        if (token == null)
            return null;

        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_appSettings.Secret!);
        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            var jwtToken = (JwtSecurityToken)validatedToken;
            var accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

            // return account id from JWT token if validation successful
            return accountId;
        }
        catch
        {
            // return null if validation fails
            return null;
        }
    }
}
 

.NET Accounts Controller

Path: /Controllers/AccountsController.cs

The accounts controller defines and handles all routes / endpoints for the API that relate to accounts including authentication with a Facebook access token and account read/write/delete operations. Within each route method the controller calls the account service to perform the action required, this enables the controller to stay 'lean' and completely separate from the business logic and data access code of each action.

Controller methods/routes are secure by default with the [Authorize] attribute on the class. The Authenticate method is decorated with the [AllowAnonymous] attribute which overrides the class-level [Authorize] attribute to make it publicly accessible. Auth logic is located in the custom authorize attribute.

namespace WebApi.Controllers;

using Microsoft.AspNetCore.Mvc;
using WebApi.Authorization;
using WebApi.Models.Accounts;
using WebApi.Services;

[ApiController]
[Authorize]
[Route("[controller]")]
public class AccountsController : BaseController
{
    private IAccountService _accountService;

    public AccountsController(IAccountService accountService)
    {
        _accountService = accountService;
    }

    [AllowAnonymous]
    [HttpPost("authenticate")]
    public async Task<IActionResult> Authenticate(AuthenticateRequest model)
    {
        var response = await _accountService.Authenticate(model);
        return Ok(response);
    }

    [HttpGet("current")]
    public IActionResult GetCurrent()
    {
        return Ok(Account);
    }

    [HttpPut("current")]
    public async Task<IActionResult> UpdateCurrent(UpdateRequest model)
    {
        var account = await _accountService.Update(Account!.Id, model);
        return Ok(account);
    }

    [HttpDelete("current")]
    public async Task<IActionResult> DeleteCurrent()
    {
        await _accountService.Delete(Account!.Id);
        return Ok();
    }
}
 

.NET Base Controller

Path: /Controllers/BaseController.cs

The base controller is inherited by all other controllers in the API and includes common properties and methods that are accessible to all controllers.

The Account property returns the current authenticated account for the request from the HttpContext.Items collection, or returns null if the request is not authenticated. The current account is added to the HttpContext.Items collection by the custom jwt middleware when the request contains a valid JWT token in the authorization header.

namespace WebApi.Controllers;

using Microsoft.AspNetCore.Mvc;
using WebApi.Entities;

[Controller]
public abstract class BaseController : ControllerBase
{
    // returns the current authenticated account (null if not logged in)
    public Account? Account => (Account?)HttpContext.Items["Account"];
}
 

.NET Account Entity

Path: /Entities/Account.cs

The account entity class represents the data for an account in the application.

namespace WebApi.Entities;

public class Account
{
    public int Id { get; set; }
    public long FacebookId { get; set; }
    public string? Name { get; set; }
    public string? ExtraInfo { get; set; }
}
 

.NET App Exception

Path: /Helpers/AppException.cs

The app exception is a custom exception class used to differentiate between handled and unhandled exceptions. Handled exceptions are ones generated by the application and used to display friendly error messages to the client, for example business logic or validation exceptions caused by incorrect input from the user. Unhandled exceptions are generated by the .NET framework and can be caused by bugs in the application code.

See the account service for examples of app exceptions that are thrown. See how different exception types are handled in the global error handler middleware.

namespace WebApi.Helpers;

using System.Globalization;

// custom exception class for throwing application specific exceptions 
// that can be caught and handled within the application
public class AppException : Exception
{
    public AppException() : base() {}

    public AppException(string message) : base(message) { }

    public AppException(string message, params object[] args) 
        : base(String.Format(CultureInfo.CurrentCulture, message, args))
    {
    }
}
 

.NET App Settings Class

Path: /Helpers/AppSettings.cs

The app settings class contains properties defined in the appsettings.json file and is used for accessing application settings via objects that are injected into classes using the .NET built in dependency injection (DI) system. For example the account service accesses app settings via an IOptions<AppSettings> appSettings object that is injected into the constructor.

Mapping of configuration sections to classes is done on startup in the Program.cs file.

namespace WebApi.Helpers;

public class AppSettings
{
    public string? Secret { get; set; }
}
 

.NET Data Context

Path: /Helpers/DataContext.cs

The data context class is used for accessing application data through Entity Framework Core and is configured to connect to an InMemory database. It derives from the EF Core DbContext class and has a public Accounts property for accessing and managing account data. The data context is used by services for handling all low level data operations.

options.UseInMemoryDatabase() configures Entity Framework to create and connect to an in-memory database so the API can be tested without a real database. This can be easily switched out to a real db provider when you're ready to work with a database such as SQL Server, Oracle, MySQL etc. For instructions on how to connect to a SQL Server database see .NET 6.0 - Connect to SQL Server with Entity Framework Core, for MySQL see .NET 6.0 - Connect to MySQL Database with Entity Framework Core.

namespace WebApi.Helpers;

using Microsoft.EntityFrameworkCore;
using WebApi.Entities;

public class DataContext : DbContext
{
    public DbSet<Account> Accounts => Set<Account>();
    
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        // in memory database used for simplicity, change to a real db for production applications
        options.UseInMemoryDatabase("TestDb");
    }
}
 

.NET Global Error Handler Middleware

Path: /Helpers/ErrorHandlerMiddleware.cs

The global error handler is used catch all errors and remove the need for duplicated error handling code throughout the .NET 7 API. It's configured as middleware in the Program.cs file.

Errors of type AppException are treated as custom (app specific) errors that return a 400 Bad Request response, the .NET built-in KeyNotFoundException class is used to return 404 Not Found responses, all other exceptions are unhandled and return a 500 Internal Server Error response as well as being logged to the console.

See the account service for examples of custom errors and not found errors thrown by the API.

namespace WebApi.Helpers;

using System.Net;
using System.Text.Json;

public class ErrorHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    public ErrorHandlerMiddleware(RequestDelegate next, ILogger<ErrorHandlerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception error)
        {
            var response = context.Response;
            response.ContentType = "application/json";

            switch (error)
            {
                case AppException e:
                    // custom application error
                    response.StatusCode = (int)HttpStatusCode.BadRequest;
                    break;
                case KeyNotFoundException e:
                    // not found error
                    response.StatusCode = (int)HttpStatusCode.NotFound;
                    break;
                default:
                    // unhandled error
                    _logger.LogError(error, error.Message);
                    response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    break;
            }

            var result = JsonSerializer.Serialize(new { message = error?.Message });
            await response.WriteAsync(result);
        }
    }
}
 

.NET Authenticate Request Model

Path: /Models/Accounts/AuthenticateRequest.cs

The authenticate request model defines the parameters for incoming POST requests to the /accounts/authenticate route, it is attached to the route by setting it as the parameter to the Authenticate action method of the accounts controller. When an HTTP POST request is received by the route, the data from the body is bound to an instance of the AuthenticateRequest class, validated and passed to the method.

.NET Data Annotations are used to automatically handle model validation, the [Required] attribute sets the Facebook AccessToken as a required field, if it's missing a validation error is returned from the API.

namespace WebApi.Models.Accounts;

using System.ComponentModel.DataAnnotations;

public class AuthenticateRequest
{
    [Required]
    public string? AccessToken { get; set; }
}
 

.NET Authenticate Response Model

Path: /Models/Accounts/AuthenticateResponse.cs

The authenticate response model defines the data returned by the Authenticate() method of the accounts controller and account service. It includes account details and a JWT Token for accessing the API.

namespace WebApi.Models.Accounts;

using WebApi.Entities;

public class AuthenticateResponse
{
    public int Id { get; set; }
    public long FacebookId { get; set; }
    public string? Name { get; set; }
    public string? ExtraInfo { get; set; }
    public string Token { get; set; }


    public AuthenticateResponse(Account account, string token)
    {
        Id = account.Id;
        FacebookId = account.FacebookId;
        Name = account.Name;
        ExtraInfo = account.ExtraInfo;
        Token = token;
    }
}
 

.NET Update Request Model

Path: /Models/Accounts/UpdateRequest.cs

The update request model defines the parameters for incoming PUT requests to the /accounts/current route, it is attached to the route by setting it as the parameter to the UpdateCurrent action method of the accounts controller. When an HTTP PUT request is received by the route, the data from the body is bound to an instance of the UpdateRequest class, validated and passed to the method.

.NET Data Annotations are used to automatically handle model validation, the [Required] attribute sets the Name as a required field, if it's missing a validation error is returned from the API.

namespace WebApi.Models.Accounts;

using System.ComponentModel.DataAnnotations;

public class UpdateRequest
{
    [Required]
    public string? Name { get; set; }

    public string? ExtraInfo { get; set; }
}
 

.NET Account Service

Path: /Services/AccountService.cs

The account service contains the core business logic for authentication with Facebook and CRUD methods for managing account data. The service encapsulates interactions with the Facebook API and the database, and exposes a simple set of methods which are used by the accounts controller.

The Authenticate() method accepts a Facebook access token in the AuthenticateRequest model, verifies the access token with an HTTP request to the Facebook API. On success a new JWT token is returned for accessing the API, along with the account details. If it's the first time logging in a new account is created in the API DB with the details fetched from the Facebook API.

The top of the file contains the IAccountService interface which defines the public methods for the account service, and below the interface is the concrete AccountService class that implements the interface.

namespace WebApi.Services;

using Microsoft.Extensions.Options;
using RestSharp;
using System.Text.Json;
using WebApi.Authorization;
using WebApi.Entities;
using WebApi.Helpers;
using WebApi.Models.Accounts;

public interface IAccountService
{
    Task<AuthenticateResponse> Authenticate(AuthenticateRequest model);
    Task<Account> Update(int id, UpdateRequest model);
    Task Delete(int id);
}

public class AccountService : IAccountService
{
    private readonly DataContext _context;
    private readonly IJwtUtils _jwtUtils;
    private readonly AppSettings _appSettings;

    public AccountService(
        DataContext context,
        IJwtUtils jwtUtils,
        IOptions<AppSettings> appSettings)
    {
        _context = context;
        _jwtUtils = jwtUtils;
        _appSettings = appSettings.Value;
    }

    public async Task<AuthenticateResponse> Authenticate(AuthenticateRequest model)
    {
        // verify access token with facebook API to authenticate
        var client = new RestClient("https://graph.facebook.com/v8.0");
        var request = new RestRequest($"me?access_token={model.AccessToken}");
        var response = await client.GetAsync(request);

        if (!response.IsSuccessful)
            throw new AppException(response.ErrorMessage!);

        // get data from response and account from db
        var data = JsonSerializer.Deserialize<Dictionary<string, string>>(response.Content!);
        var facebookId = long.Parse(data!["id"]);
        var name = data["name"];
        var account = _context.Accounts.SingleOrDefault(x => x.FacebookId == facebookId);

        // create new account if first time logging in
        if (account == null)
        {
            account = new Account
            {
                FacebookId = facebookId,
                Name = name,
                ExtraInfo = $"This is some extra info about {name} that is saved in the API"
            };
            _context.Accounts.Add(account);
            await _context.SaveChangesAsync();
        }

        // generate jwt token to access secure routes on this API
        var token = _jwtUtils.GenerateJwtToken(account);

        return new AuthenticateResponse(account, token);
    }

    public async Task<Account> Update(int id, UpdateRequest model)
    {
        var account = await getAccount(id);

        // update
        account.Name = model.Name;
        account.ExtraInfo = model.ExtraInfo;

        // save
        _context.Accounts.Update(account);
        await _context.SaveChangesAsync();

        return account;
    }

    public async Task Delete(int id)
    {
        var account = await getAccount(id);
        _context.Accounts.Remove(account);
        await _context.SaveChangesAsync();
    }

    // helper methods

    private async Task<Account> getAccount(int id)
    {
        var account = await _context.Accounts.FindAsync(id);
        if (account == null)
            throw new KeyNotFoundException("Account not found");
        return account;
    }
}
 

.NET 7 App Settings JSON

Path: /appsettings.json

The appsettings.json file is the base configuration file in a .NET app that contains settings for all environments (e.g. Development, Production). You can override values for different environments by creating environment specific appsettings files (e.g. appsettings.Development.json, appsettings.Production.json).

IMPORTANT: The "Secret" property is used to sign and verify JWT tokens for authentication, change it to a random string to ensure nobody else can generate a JWT with the same secret and gain unauthorized access to your API. A quick and easy way is join a couple of GUIDs together to make a long random string (e.g. from https://www.guidgenerator.com/).

{
    "AppSettings": {
        "Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    }
}
 

.NET 7 Program

Path: /Program.cs

The .NET 7 Program file contains top-level statements which are converted by the C# compiler into a Main() method and Program class for the .NET program. The Main() method is the entry point for a .NET application, when an app is started it searches for the Main() method to begin execution. The top-level statements can be located anywhere in the project but are typically placed in the Program.cs file, and only one file can contain top-level statements within a .NET application.

The WebApplication class handles app startup, lifetime management, web server configuration and more. A WebApplicationBuilder is first created by calling the static method WebApplication.CreateBuilder(args), the builder is used to configure services for dependency injection (DI), a WebApplication instance is created by calling builder.Build(), the app instance is used to configure the HTTP request pipeline (middleware), then the app is started by calling app.Run().

I wrapped the add services... and configure HTTP... sections in curly brackets {} to group them together visually, but the brackets are completely optional.

Internally the WebApplicationBuilder class calls the ConfigureWebHostDefaults() extension method which configures hosting for the web app including setting Kestrel as the web server, adding host filtering middleware and enabling IIS integration. For more info on the default builder settings see https://docs.microsoft.com/aspnet/core/fundamentals/host/generic-host#default-builder-settings.

using WebApi.Authorization;
using WebApi.Helpers;
using WebApi.Services;

var builder = WebApplication.CreateBuilder(args);

// add services to DI container
{
    var services = builder.Services;

    services.AddDbContext<DataContext>();
    services.AddCors();
    services.AddControllers();

    // configure strongly typed settings object
    services.Configure<AppSettings>(builder.Configuration.GetSection("AppSettings"));

    // configure DI for application services
    services.AddScoped<IJwtUtils, JwtUtils>();
    services.AddScoped<IAccountService, AccountService>();
}

var app = builder.Build();

// configure HTTP request pipeline
{
    // global cors policy
    app.UseCors(x => x
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());

    // global error handler
    app.UseMiddleware<ErrorHandlerMiddleware>();

    // custom jwt auth middleware
    app.UseMiddleware<JwtMiddleware>();

    app.MapControllers();
}

app.Run("http://localhost:4000");
 

.NET 7 Web Api csproj

Path: /WebApi.csproj

The csproj (C# project) is an MSBuild based file that contains target framework and NuGet package dependency information for the application. The ImplicitUsings feature is enabled which tells the compiler to auto generate a set of global using directives based on the project type, removing the need to include a lot of common using statements in each class file. The global using statements are auto generated when you build the project and can be found in the file /obj/Debug/net7.0/WebApi.GlobalUsings.g.cs.

For more info on the C# project file see .NET + MSBuild - C# Project File (.csproj) in a Nutshell.

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <RootNamespace>WebApi</RootNamespace>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
        <PackageReference Include="RestSharp" Version="108.0.3" />
        <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
    </ItemGroup>
</Project>

 


Subscribe or Follow Me For Updates

Subscribe to my YouTube channel or follow me on Twitter, Facebook or GitHub to be notified when I post new content.

Other than coding...

I'm currently attempting to travel around Australia by motorcycle with my wife Tina on a pair of Royal Enfield Himalayans. You can follow our adventures on YouTube, Instagram and Facebook.


Need Some .NET Help?

Search fiverr to find help quickly from experienced .NET developers.



Supported by