Secure a Web API with a JWT Token

    This tutorial teaches the basics of using JWT token to secure a web API with ASP.NET Core in SnapDevelop.

    In this tutorial, you will learn how to:

    • Create a Web API Project
    • Test the API
    • Configure Authentication and JWT
    • Enable HTTPS and Authentication
    • Add a Service
    • Add a Controller
    • Enable Authentication for the Sample Controller
    • Testing the Sample API
    • Call the service API from PowerBuilder

    Prerequisites

    • SnapDevelop 2019

    Create a Web API Project

    Start SnapDevelop and select Create New Project from the Start page. Or, from the File menu, select New and then Project....

    In the New Project dialog, select .NET Core, and in the list of project templates, select ASP.NET Core Web API. Name the project "WebAPI1", name the solution "WebAPI Tutorials" and click OK.

    Test the API

    The project template creates a sample API. Call the Load method from a browser to test the app.

    Press Ctrl+F5 to run the app. SnapDevelop launches a browser and navigates to http://localhost:5000/api/sample/load.

    If it's the first time to run, you may need to wait several seconds for initiating .NET runtime after the browser launched.

    The following JSON is returned:

    ["value1","value2"]
    

    Configure Authentication and JWT

    Update Startup.cs, add the following using statements.

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.IdentityModel.Tokens;
    using System.Text;
    

    Define Secret Key in Startup.cs

    public class Startup
    {
        //put secret here for simplicity, usually it should be in appsettings.json
        public const string SECRET = "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE  IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING";
        
        //other code
        ......
    

    Add Configuration Code

    Update the ConfigureServices(IServiceCollection services) method in Startup.cs. Add the following code to the end of the method:

                var key = Encoding.ASCII.GetBytes(SECRET);
                
                services.AddAuthentication(x =>
                {
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(x =>
                {
                    x.RequireHttpsMetadata = true;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(key),
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                });
    

    Enable HTTPS and Authentication

    Update Configure(IApplicationBuilder app, IHostingEnvironment env) method in Startup.cs. Uncomment the following line of code.

    app.UseHttpsRedirection();
    

    Add the following code under app.UseHttpsRedirection().

    app.UseAuthentication();
    

    The changed code is as below.

    app.UseHttpsRedirection();
    app.UseAuthentication();
    

    Add a Service

    Add a User Model

    Right-click on the Web API project. Click Add > New Folder. Name it Models. Then right-click on the new folder and click Add > Class.

    On the popup window, name it User.cs and click Ok.

    The User class represents the data for a user, and act as a data model in the application. Replace the auto-generated code with the following code.

    namespace WebAPI1.Models
    {
        public class User
        {
            public int Id { get; set; }
            public string UserName { get; set; }
            public string Password { get; set; }
            public string Role { get; set; }
            public string Token { get; set; }
        }
    }
    

    Add Service Interface and Implementation

    Right-click on the Web API project. Click Add > New Folder. Name it Services. Now add a sub-folder to it and name it Impl. Next, Right-click on the Services folder and click on Add > Interface.

    Select Interface on the template list and Name the interface IUserService, click OK. The interface defines the user service. Replace the auto-generated code with the following code.

    namespace WebAPI1.Services
    {
        public interface IUserService
        {
            string Login(string userName, string password);
        }
    }
    

    Right-click on the Services/Impl folder and click on Add > Class. Name the class UserService. The UserService implements IUserService interface. Replace the auto-generated code with the following code.

    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using Microsoft.IdentityModel.Tokens;
    using WebAPI1.Models;
    
    namespace WebAPI1.Services
    {
        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, UserName = "user1", Password = "password1", Role = "admin"},
                new User { Id = 2, UserName = "user2", Password = "password2", Role = "guest"}
            };
    
            public string Login(string userName, string password)
            {
                var user = _users.SingleOrDefault(x => x.UserName == userName && x.Password == password);
    
                // return null if user not found
                if (user == null)
                {
                    return string.Empty;
                }
    
                // authentication successful so generate jwt token
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(Startup.SECRET);
    
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                        new Claim(ClaimTypes.Name, user.UserName),
                        new Claim(ClaimTypes.Role, user.Role)
                    }),
    
                    Expires = DateTime.UtcNow.AddMinutes(5),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
    
                var token = tokenHandler.CreateToken(tokenDescriptor);
                user.Token = tokenHandler.WriteToken(token);
    
                return user.Token;
            }
        }
    }
    

    In ASP.NET Core, services must be registered with the dependency injection (DI) container. The container provides the service to controllers.

    Update Startup.cs, add the following using statement.

    using WebAPI1.Services;
    

    Update the ConfigureServices(IServiceCollection services) method in Startup.cs. Add the following code to the end of method :

    services.AddScoped<IUserService, UserService>();
    

    Add a Controller

    Right-click on the Controllers folder and click on Add > New Item.... Select API Controller Class on the list of item templates and name the controller UserController, click OK. Replace the auto-generated code with the following code.

    using System;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Authorization;
    using WebAPI1.Services;
    using WebAPI1.Models;
    
    namespace WebAPI1.Controllers
    {
        [Route("api/[controller]/[action]")]
        [ApiController]
        public class UserController : ControllerBase
        {
            private IUserService _userService;
    
            public UserController(IUserService userService)
            {
                _userService = userService;
            }
    
            // POST api/user/login
            [AllowAnonymous]
            [HttpPost]
            public IActionResult Login([FromBody]User user)
            {
                var token = _userService.Login(user.UserName, user.Password);
    
                if (token == null || token == String.Empty)
                    return BadRequest(new { message = "User name or password is incorrect" });
    
                return Ok(token);
            }
        }
    }
    

    Enable Authentication for the Sample Controller

    Back to SampleController class. Add the following using statement.

    using Microsoft.AspNetCore.Authorization;
    

    Add an Authorize attribute with roles ""admin,manager"" property on Load() action. Only authorized user with role admin or manager can access this API.

            // GET api/sample/load
            [HttpGet]
            [Authorize(Roles = "admin,manager")]
            public ActionResult<IEnumerable<string>> Load()
            {
                return new string[] { "value1", "value2" };
            }
    

    Add an Authorize attribute without roles property on LoadOne(int id) action. Any authorized user can access this API.

            // GET api/sample/loadone/{id}
            [HttpGet("{id}")]
            [Authorize]
            public ActionResult<string> LoadOne(int id)
            {
                return "value";
            }
    

    View of the Solution Tree:

    Testing the Sample API

    Press Ctrl+F5 to run the app. SnapDevelop launches a browser and navigates to https://localhost:5001/api/sample/load. Please pay attention here, it's https://....

    You will get HTTP ERROR 401 as expected.

    Change and navigates to https://localhost:5001/api/sample/loadone/1

    You will get HTTP ERROR 401 as expected too.

    Call the service API from PowerBuilder

    Get the JWT token

    The RESTClient object provides the ability to access the RESTful Web APIs. It can get the JWT token using the POST method.

    To get the JWT token, you need to instantiate the RESTClient object first. Add an instance variable of the RESTClient object.

    RESTClient inv_RestClient
    

    On the Open event of your window, create the object variable and Set the Request Header.

    // Create the RESTClient object variable
    inv_RestClient	= CREATE RESTClient
    
    // Set the Request Headers to tell the Web API you will send JSON data
    inv_RESTClient.SetRequestHeader ("Content-Type", "application/json;charset=UTF-8")
    

    On the source code window of your Send Request button, add the RESTClient.GetJWTToken() function to call the Web API to get a token. Add the RESTClient.SetJWTToken() function to set the JWT token string to the HTTP request header.

    String ls_url_token = "https://localhost:5001/api/user/login"
    String ls_user = '{"UserName":"user1", "Password":"password1"}'
    String ls_token
    
    // Get a JWT token.
    If inv_RestClient.GetJWTToken(ls_url_token, ls_user, ls_token) = 1 Then
    
        // Set the JWT token string to the HTTP request header.
        inv_RestClient.SetJWTToken(ls_token)
    Else
        // Get JWT token failed.
        Return
    End If
    

    Call the service API with the JWT token

    On the source code window of the Send Request button, continue adding the RESTClient.SendGetRequest() function to call your Web API to get data (with the JWT token in HTTP request header).

    String ls_url = "https://localhost:5001/api/sample/load"
    String ls_response
    String ls_msg
    
    // Request api/sample
    inv_RestClient.SendGetRequest(ls_url, ls_response)
    
    // Show response info.
    ls_msg = "Status Code: " + String(inv_RestClient.GetResponseStatusCode()) + '~r~n' + &
                "Status Text: " + String(inv_RestClient.GetResponseStatusText()) + '~r~n' + &
                "Response Body: " + ls_response
    
    Messagebox("Response Info", ls_msg)
    

    Press Ctrl+R to run the app. Click on the Send Request button and check the response. (Note: Your Web API needs to be running.)

    Status Code: 200
    Status Text: OK
    Response Body: ["value1","value2"] 
    

    Call Web APIs with different role authorization

    There are two users created at server side (see Add Service Interface and Implementation). 'user1' has admin role, and 'user2' has guest role.

    In the Sample Controller class at server side (see Enable Authentication for the Sample Controller), it only authorized the user with admin or manager role to access the API "GET api/sample/load". The API "GET api/sample/loadone/{id}" is allowed to access by user with any role.

    Call the Web API with a user that has an admin role

    1. Based on the response of previous application run, user1 that has an admin role is allowed to request the API "GET api/sample/load".

    2. On the source code window of your Send Request button, change the URL of the request to: "GET api/sample/loadone/{id}".

    String ls_url = "https://localhost:5001/api/sample/loadone/1"
    

    Press Ctrl+R to run the app. Click on the Send Request button and check the response. (Note: Your Web API needs to be running.)

    Status Code: 200
    Status Text: OK
    Response Body: value
    

    Notice that user1 is authorized to access both "GET api/sample/load" and "GET api/sample/loadone/{id}" APIs.

    Call the Web API with a user that has a guest role

    Close your application. On the source code window of your Send Request button, change the user from user1 (admin) to user2 (guest).

    String ls_user = '{"UserName":"user2", "Password":"password2"}'
    

    On the source code window of your Send Request button, change URL to request both APIs and check the response.

    1. First request is "GET api/sample/load":
    String ls_url = "https://localhost:5001/api/sample/load"
    

    Press Ctrl+R to run the app. Click on the Send Request button and check the response. (Note: Your Web API needs to be running.)

    Status Code: 403
    Status Text: Forbidden
    Response Body: 
    

    Notice that user2 is forbidden to access the API "GET api/sample/load". Because only admin and manager user role can access this API.

    1. Second request is "GET api/sample/loadone/{id}":
    String ls_url = "https://localhost:5001/api/sample/loadone/1"
    

    Press Ctrl+R to run the app. Click on the Send Request button and check the response. (Note: Your Web API needs to be running.)

    Status Code: 200
    Status Text: OK
    Response Body: value
    

    User2 can access the API "GET api/sample/loadone/{id}", because user is authorized to access this API with any role.

    Back to top Generated by Appeon