Authentication


WebApi and MVC – JWT(JSON Web Token)

Introduction

 In this article, we will learn how to implement Token Based Authentication in Web APIs to secure the data. There are 4 common methods of Web API Authentication:

  1. HTTP Authentication Schemes (Basic & Bearer)
  2. API Keys
  3. OAuth (2.0)
  4. OpenID Connect

Here we will learn OAuth authentication. OAuth is an open standard for token based authentication and authorization on internet. By using OAuth we can create Token Based Authentication API. 

What is Token Based Authentication in Web API?

 Token-based authentication is a process where the client application first sends a request to Authentication server with a valid credentials. The Authentication server sends an Access token to the client as a response. This token contains enough data to identify a particular user and it has an expiry time. The client application then uses the token to access the restricted resources in the next requests until the token is valid. If the Access token is expired, then the client application can request for a new access token by using Refresh token.

Advantages of Token Based Authentication

  • Scalability of Servers
  • Loosely Coupling
  • Mobile-Friendly

Let’s discuss the step by step procedure to create Token-Based Authentication, Step 1 – Create ASP.NET Web Project in Visual Studio 2019  We have to create web project in Visual Studio as given in the below image. Choose ASP.Net Web Application from the menu. 

  Give the project name as:WEBAPITOKENAUTHENTICATION. 

 Now choose the empty template and check the “MVC” and “Web API” on the right hand side. 

Step 2 – Addition Of References In this step,we have to add Nuget References like the below image, 

  Here we have to add the following references : 

  • Microsoft.Owin.Host.SystemWeb 
  • Microsoft.Owin.Security.OAuth
  • Microsoft.Owin.Cors

Step 3 – Create APIAUTHORIZATIONSERVERPROVIDER.cs Class File Now, let’s create the class file to provide credentials to access data depending on username, password, and roles.  

  Code is given below:

  1. public class APIAUTHORIZATIONSERVERPROVIDER : OAuthAuthorizationServerProvider  
  2. {  
  3.     public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)  
  4.     {  
  5.         context.Validated(); //   
  6.     }  
  7.     public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)  
  8.     {  
  9.         var identity = new ClaimsIdentity(context.Options.AuthenticationType);  
  10.         if (context.UserName == “admin” && context.Password == “admin”)  
  11.         {  
  12.             identity.AddClaim(new Claim(ClaimTypes.Role, “admin”));  
  13.             identity.AddClaim(new Claim(“username”, “admin”));  
  14.             identity.AddClaim(new Claim(ClaimTypes.Name, “Hi Admin”));  
  15.             context.Validated(identity);  
  16.         }  
  17.         else if (context.UserName == “user” && context.Password == “user”)  
  18.         {  
  19.             identity.AddClaim(new Claim(ClaimTypes.Role, “user”));  
  20.             identity.AddClaim(new Claim(“username”, “user”));  
  21.             identity.AddClaim(new Claim(ClaimTypes.Name, “Hi User”));  
  22.             context.Validated(identity);  
  23.         }  
  24.         else  
  25.         {  
  26.             context.SetError(“invalid_grant”, “Provided username and password is incorrect”);  
  27.             return;  
  28.         }  
  29.     }  
  30. }  

Step 4 – Create a AuthenticationStartup.cs Class File Here, we need to create a new class file to implement the code configuration provider and create an instance of class APIAUTHORIZATIONSERVERPROVIDER. 

  Code is given below:

  1. public class AuthenticationStartup  
  2. {  
  3.     public void Configuration(IAppBuilder app)  
  4.     {    
  5.         app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);  
  6.         var myProvider = new APIAUTHORIZATIONSERVERPROVIDER();  
  7.         OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions  
  8.         {  
  9.             AllowInsecureHttp = true,  
  10.             TokenEndpointPath = new PathString(“/token”),  
  11.             AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),  
  12.             Provider = myProvider  
  13.         };  
  14.         app.UseOAuthAuthorizationServer(options);  
  15.         app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
  16.         HttpConfiguration config = new HttpConfiguration();  
  17.         WebApiConfig.Register(config);  
  18.    }  
  19. }  

Step 5 – Create a APIAUTHORIZEATTRIBUTE.cs Class File We need to create this class to handle unauthorized access to resources and show the proper message. 

  Code is given below:

  1. public class APIAUTHORIZEATTRIBUTE : System.Web.Http.AuthorizeAttribute  
  2. {  
  3.     protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)  
  4.     {  
  5.         if (!HttpContext.Current.User.Identity.IsAuthenticated)  
  6.         {  
  7.             base.HandleUnauthorizedRequest(actionContext);  
  8.         }  
  9.         else  
  10.         {  
  11.             actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);  
  12.         }  
  13.     }  
  14. }  

Step 6 – Create a controller with name UserController Now we have to create an empty web api controller with name usercontroller. In this controller, we will write the actions with different authorization and roles. 

 The first action “Get” will be available for anonymous users .No authetication or token is needed for this.

  1. [AllowAnonymous]  
  2. [HttpGet]  
  3. [Route(“api/data/forall”)]  
  4. public IHttpActionResult Get()  
  5. {  
  6.     return Ok(“Now server time is: ” + DateTime.Now.ToString());  

The second action “GetForAuthenticate” only allows the authorized user to access it. 

  1. [Authorize]  
  2. [HttpGet]  
  3. [Route(“api/data/authenticate”)]  
  4. public IHttpActionResult GetForAuthenticate()  
  5. {  
  6.     var identity = (ClaimsIdentity)User.Identity;  
  7.     return Ok(“Hello ” + identity.Name);  
  8. }  

The third action “GetForAdmin”  checks authorization and allows only admins to access it.

  1. [Authorize(Roles = “admin”)]  
  2. [HttpGet]  
  3. [Route(“api/data/authorize”)]  
  4. public IHttpActionResult GetForAdmin()  
  5. {  
  6.     var identity = (ClaimsIdentity)User.Identity;  
  7.     var roles = identity.Claims  
  8.                 .Where(c => c.Type == ClaimTypes.Role)  
  9.                 .Select(c => c.Value);  
  10.     return Ok(“Hello ” + identity.Name + ” Role: ” + string.Join(“,”, roles.ToList()));  
  11. }  

Step 7 – Accessing the controller using Postman  Now we have to access the controller. For that purpose, we are using postman to get data.I n case ofthe  first action “Get”,we can access the data without generating a token as no authorization is needed for it, by just sending GET request for route “api/data/forall”. 

 But for the rest of the actions, we have to generate a token using credentials. So we have to do post request for the token. 

 Now once the token is generated for the “user” now we can easily access the actions by using the user token. 

 Similarly, we can access the action as “admin” by generating token as admin and then using it. 

 In the same way, the other actions can be accessed by user or admin depending on the way the token is generated.

ASP.NET Core 3.1 – JWT Authentication Tutorial with Example API

https://jasonwatmore.com/post/2019/10/11/aspnet-core-3-jwt-authentication-tutorial-with-example-api

Angular 9 – JWT Authentication Example & Tutorial | Jason Watmore’s Blog

In this tutorial we’ll go through a simple example of how to implement custom JWT (JSON Web Token) authentication in an ASP.NET Core 3.1 API with C#.

For an extended example that includes refresh tokens see ASP.NET Core 3.1 API – JWT Authentication with Refresh Tokens.

The example API has just two endpoints/routes to demonstrate authenticating with JWT and accessing a restricted route with JWT:

  • /users/authenticate – public route that accepts HTTP POST requests containing the username and password in the body. If the username and password are correct then a JWT authentication token and the user details are returned.
  • /users – secure route that accepts HTTP GET requests and returns a list of all the users in the application if the HTTP Authorization header contains a valid JWT token. If there is no auth token or the token is invalid then a 401 Unauthorized response is returned.

The tutorial project is available on GitHub at https://github.com/cornflourblue/aspnet-core-3-jwt-authentication-api.

How to authenticate a user with Postman

To authenticate a user with the api and get a JWT token follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to “POST” with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the authenticate route of your local API – http://localhost:4000/users/authenticate.
  4. Select the “Body” tab below the URL field, change the body type radio button to “raw”, and change the format dropdown selector to “JSON (application/json)”.
  5. Enter a JSON object containing the test username and password in the “Body” textarea:{ "username": "test", "password": "test" }
  6. Click the “Send” button, you should receive a “200 OK” response with the user details including a JWT token in the response body, make a copy of the token value because we’ll be using it in the next step to make an authenticated request.

Here’s a screenshot of Postman after the request is sent and the user has been authenticated:


How to make an authenticated request to retrieve all users

To make an authenticated request using the JWT token from the previous step, follow these steps:

  1. Open a new request tab by clicking the plus (+) button at the end of the tabs.
  2. Change the http request method to “GET” with the dropdown selector on the left of the URL input field.
  3. In the URL field enter the address to the users route of your local API – http://localhost:4000/users.
  4. Select the “Authorization” tab below the URL field, change the type to “Bearer Token” in the type dropdown selector, and paste the JWT token from the previous authenticate step into the “Token” field.
  5. Click the “Send” button, you should receive a “200 OK” response containing a JSON array with all the user records in the system (just the one test user in the example).

Here’s a screenshot of Postman after making an authenticated request to get all users:

ASP.NET Core JWT Authentication Project Structure

The tutorial project is organised into the following folders:
Controllers – define the end points / routes for the web api, controllers are the entry point into the web api from client applications via http requests.
Models – represent request and response models for controller methods, – >Request models-> define the parameters for incoming requests, and
->Response models-> can be used to define what data is returned.
Services – contain business logic, validation and data access code.
Entities – represent the application data.
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:

ASP.NET Core JWT Users Controller

Path: /Controllers/UsersController.cs

The ASP.NET Core users controller defines and handles all routes / endpoints for the api that relate to users, this includes authentication and standard CRUD operations. Within each route the controller calls the user service to perform the action required which keeps the controller ‘lean’ and completely separated from the business logic and data access code.

Routes restricted to authenticated users are decorated with the [Authorize] attribute. The auth logic is implemented in the custom authorize attribute.

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

namespace WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class UsersController : ControllerBase
    {
        private IUserService _userService;

        public UsersController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpPost("authenticate")]
        public IActionResult Authenticate(AuthenticateRequest model)
        {
            var response = _userService.Authenticate(model);

            if (response == null)
                return BadRequest(new { message = "Username or password is incorrect" });

            return Ok(response);
        }

        [Authorize]
        [HttpGet]
        public IActionResult GetAll()
        {
            var users = _userService.GetAll();
            return Ok(users);
        }
    }
}

ASP.NET Core JWT User Entity

Path: /Entities/User.cs

The user entity class represents the data for a user in the application. Entity classes are used to pass data between different parts of the application (e.g. between services and controllers) and can be used to return http response data from controller action methods. If multiple types of entities or other custom data is required to be returned from a controller method then a custom model class should be created in the Models folder for the response.

The [JsonIgnore] attribute prevents the password property from being serialized and returned in api responses.

using System.Text.Json.Serialization;

namespace WebApi.Entities
{
    public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }

        [JsonIgnore]
        public string Password { get; set; }
    }
}

ASP.NET Core JWT App Settings

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 ASP.NET Core built in dependency injection (DI) system. For example the User Service accesses app settings via an IOptions<AppSettings> appSettings object that is injected into the constructor.

Mapping of configuration sections to classes is done in the ConfigureServices method of the Startup.cs file.

namespace WebApi.Helpers
{
    public class AppSettings
    {
        public string Secret { get; set; }
    }
}

ASP.NET Core Custom Authorize Attribute

Path: /Helpers/AuthorizeAttribute.cs

The custom authorize attribute is added to controller action methods that require the user to be authenticated.

Authorization is performed by the OnAuthorization method which checks if there is an authenticated user attached to the current request (context.HttpContext.Items["User"]). An authenticated user is attached by the custom jwt middleware if the request contains a valid JWT access token.

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.

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

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = (User)context.HttpContext.Items["User"];
        if (user == null)
        {
            // not logged in
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}

ASP.NET Core Custom JWT Middleware

Path: /Helpers/JwtMiddleware.cs

The custom JWT middleware checks if there is a token in the request Authorization header, and if so attempts to:

  1. Validate the token
  2. Extract the user id from the token
  3. Attach the authenticated user to the current HttpContext.Items collection to make it accessible within the scope of the current request

If there is no token in the request header or if any of the above steps fail then no user is attached to the http context and the request is only be able to access public routes. Authorization is performed by the custom authorize attribute which checks that a user is attached to the http context, if authorization fails a 401 Unauthorized response is returned.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebApi.Services;

namespace 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, IUserService userService)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            if (token != null)
                attachUserToContext(context, userService, token);

            await _next(context);
        }

        private void attachUserToContext(HttpContext context, IUserService userService, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                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 userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

                // attach user to context on successful jwt validation
                context.Items["User"] = userService.GetById(userId);
            }
            catch
            {
                // do nothing if jwt validation fails
                // user is not attached to context so request won't have access to secure routes
            }
        }
    }
}

ASP.NET Core JWT Authenticate Request Model

Path: /Models/AuthenticateRequest.cs

The authenticate request model defines the parameters for incoming requests to the /users/authenticate route, it is attached to the route as the parameter to the Authenticate action method of the users 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.

ASP.NET Core Data Annotations are used to automatically handle model validation, the [Required] attribute sets both the username and password as required fields so if either are missing a validation error message is returned from the api.

using System.ComponentModel.DataAnnotations;

namespace WebApi.Models
{
    public class AuthenticateRequest
    {
        [Required]
        public string Username { get; set; }

        [Required]
        public string Password { get; set; }
    }
}

ASP.NET Core JWT Authenticate Response Model

Path: /Models/AuthenticateResponse.cs

The authenticate response model defines the data returned after successful authentication, it includes basic user details and a JWT access token.

using WebApi.Entities;

namespace WebApi.Models
{
    public class AuthenticateResponse
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Username { get; set; }
        public string Token { get; set; }


        public AuthenticateResponse(User user, string token)
        {
            Id = user.Id;
            FirstName = user.FirstName;
            LastName = user.LastName;
            Username = user.Username;
            Token = token;
        }
    }
}

ASP.NET Core JWT User Service

Path: /Services/UserService.cs

The user service contains methods for authenticating user credentials and returning a JWT token, getting all users in the application and getting a single user by id.

I hardcoded the array of users in the example to keep it focused on JWT authentication, in a production application it is recommended to store user records in a database with hashed passwords. For an extended example that includes support for user registration and stores data with Entity Framework Core check out ASP.NET Core 3.1 – Simple API for Authentication, Registration and User Management.

The top of the file contains an interface that defines the user service, below that is the concrete user service class that implements the interface.

On successful authentication the Authenticate() method generates a JWT (JSON Web Token) using the JwtSecurityTokenHandler class which generates a token that is digitally signed using a secret key stored in appsettings.json. The JWT token is returned to the client application which must include it in the HTTP Authorization header of subsequent requests to secure routes.

sing Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using WebApi.Entities;
using WebApi.Helpers;
using WebApi.Models;

namespace WebApi.Services
{
    public interface IUserService
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
        IEnumerable<User> GetAll();
        User GetById(int id);
    }

    public class UserService : IUserService
    {
        // users hardcoded for simplicity, store in a db with hashed passwords in production applications
        private List<User> _users = new List<User>
        {
            new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
        };

        private readonly AppSettings _appSettings;

        public UserService(IOptions<AppSettings> appSettings)
        {
            _appSettings = appSettings.Value;
        }

        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {
            var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);

            // return null if user not found
            if (user == null) return null;

            // authentication successful so generate jwt token
            var token = generateJwtToken(user);

            return new AuthenticateResponse(user, token);
        }

        public IEnumerable<User> GetAll()
        {
            return _users;
        }

        public User GetById(int id)
        {
            return _users.FirstOrDefault(x => x.Id == id);
        }

        // helper methods

        private string generateJwtToken(User user)
        {
            // generate token that is valid for 7 days
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
                Expires = DateTime.UtcNow.AddDays(7),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
}

ASP.NET Core JWT App Settings (Development)

Path: /appsettings.Development.json

Configuration file with application settings that are specific to the development environment.

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

ASP.NET Core JWT App Settings

Path: /appsettings.json

Root configuration file containing application settings for all environments.

IMPORTANT: The "Secret" property is used by the api to sign and verify JWT tokens for authentication, update it with your own random string to ensure nobody else can generate a JWT to gain unauthorised access to your application.

{
  "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": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

ASP.NET Core JWT Program

Path: /Program.cs

The program class is a console app that is the main entry point to start the application, it configures and launches the web api host and web server using an instance of IHostBuilder. ASP.NET Core applications require a host in which to execute.

Kestrel is the web server used in the example, it’s a new cross-platform web server for ASP.NET Core that’s included in new project templates by default. Kestrel is fine to use on it’s own for internal applications and development, but for public facing websites and applications it should sit behind a more mature reverse proxy server (IIS, Apache, Nginx etc) that will receive HTTP requests from the internet and forward them to Kestrel after initial handling and security checks.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace WebApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>()
                        .UseUrls("http://localhost:4000");
                });
    }
}

ASP.NET Core JWT Startup

Path: /Startup.cs

The startup class configures the services available to the ASP.NET Core Dependency Injection (DI) container in the ConfigureServices method, and configures the ASP.NET Core request pipeline for the application in the Configure method.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using WebApi.Helpers;
using WebApi.Services;

namespace WebApi
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // add services to the DI container
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddControllers();

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

            // configure DI for application services
            services.AddScoped<IUserService, UserService>();
        }

        // configure the HTTP request pipeline
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();

            // global cors policy
            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());

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

            app.UseEndpoints(x => x.MapControllers());
        }
    }
}

ASP.NET Core JWT 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.

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.6" />
    <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.1" />
  </ItemGroup>
</Project>

https://www.youtube.com/c/JasonWatmore