Token Based Authentication Using Asp.Net Core 2.0

ASP.Net Core 2.0 came out recently and there were quite a few changes to the

AJ

authentication scheme.  In this article, I’ll talk about how to setup token based authentication using JWT’s in ASP.Net Core 2.0.  We’ll use the Identity system to handle authentication, and Entity Framework to access an MS SQL backend.  NOTE: you will probably need to install the .Net Core 2.0 Libraries.

The source code for this can be found here.

 Setup

 

  1. Open Visual Studio 2017 and select File -> New -> Project.
  2. From there, select “Asp.NET Core Application” and give a desired name.  (I call it “api”.)

file-select

Next, select “Web API” and no authentication (we’ll set it up manually).

web-api.PNG

This will create a base application.

Entity Framework Setup

Under the project, create a folder named “Models”.  In that folder, create a file named “ApplicationUser.cs” and paste the following:

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace api.Models
{
    public class ApplicationUser : IdentityUser
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsEnabled { get; set; }
    }
}

This is the user model used for authentication.

Next, let’s create the application DbContext.  Under the models folder,  add a file named “ApiDbContext” and paste the following:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace api.Models
{
    public class ApiDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApiDbContext(DbContextOptions options) : base(options)
        {
        }

        protected ApiDbContext()
        {
        }       

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }

}

This sets up the database Db Context which will be used in authentication users.  When the system starts, we can setup a seed user to use for testing.  Let’s add the code for that.

Under models, create a file named “ApiSeedData.cs” and paste the following:

using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using api.Utils;

namespace api.Models
{
    public class ApiDbSeedData
    {
        public ApiDbSeedData(UserManager<ApplicationUser> userManager)
        {

        }

        public static async Task Seed(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
        {
            await SeedRolesAndClaims(userManager, roleManager);
            await SeedAdmin(userManager);
        }

        private static async Task SeedRolesAndClaims(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
        {

            if (!await roleManager.RoleExistsAsync(Extensions.AdminRole))
            {
                await roleManager.CreateAsync(new IdentityRole
                {
                    Name = Extensions.AdminRole
                });
            }

            if (!await roleManager.RoleExistsAsync(Extensions.UserRole))
            {
                await roleManager.CreateAsync(new IdentityRole
                {
                    Name = Extensions.UserRole
                });
            }

            var adminRole = await roleManager.FindByNameAsync(Extensions.AdminRole);
            var adminRoleClaims = await roleManager.GetClaimsAsync(adminRole);

            if (!adminRoleClaims.Any(x => x.Type == Extensions.ManageUserClaim))
            {
                await roleManager.AddClaimAsync(adminRole, new System.Security.Claims.Claim(Extensions.ManageUserClaim, "true"));
            }
            if (!adminRoleClaims.Any(x => x.Type == Extensions.AdminClaim))
            {
                await roleManager.AddClaimAsync(adminRole, new System.Security.Claims.Claim(Extensions.AdminClaim, "true"));
            }

            var userRole = await roleManager.FindByNameAsync(Extensions.UserRole);
            var userRoleClaims = await roleManager.GetClaimsAsync(userRole);
            if (!userRoleClaims.Any(x => x.Type == Extensions.UserClaim))
            {
                await roleManager.AddClaimAsync(userRole, new System.Security.Claims.Claim(Extensions.UserClaim, "true"));
            }
        }

        private static async Task SeedAdmin(UserManager<ApplicationUser> userManager)
        {
            var u = await userManager.FindByNameAsync("admin");
            if (u == null)
            {
                u = new ApplicationUser
                {
                    UserName = "admin",
                    Email = "admin@nothing.com",
                    SecurityStamp = Guid.NewGuid().ToString(),
                    IsEnabled = true,
                    FirstName = "admin",
                    LastName = "user"
                };
                var x = await userManager.CreateAsync(u, "Admin1234!");
            }
            var uc = await userManager.GetClaimsAsync(u);
            if (!uc.Any(x => x.Type == Extensions.AdminClaim))
            {
                await userManager.AddClaimAsync(u, new System.Security.Claims.Claim(Extensions.AdminClaim, true.ToString()));
            }
            if(!await userManager.IsInRoleAsync(u, Extensions.AdminRole))
                await userManager.AddToRoleAsync(u, Extensions.AdminRole);
        }
    }
}

This uses a constant create the admin claim.  To do this, create a folder named “Utils” and add  a file named “Extensions”. Paste the following;

using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace api.Utils
{
    public static class Extensions
    {
        public const string AdminClaim = "admin";
        public const string UserClaim = "user";
        public const string ManageUserClaim = "manage_user";
        public const string AdminRole = "admin";
        public const string UserRole = "user";

        public const string RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";

        public static string Error(this ModelStateDictionary modelState)
        {
            foreach (var key in modelState.Keys)
            {
                if (modelState[key].Errors.Count > 0)
                    return modelState[key].Errors[0].ErrorMessage;
            }
            return string.Empty;
        }
    }
}

This sets up the admin claim name and error handling (later).

Now, we need to set the connection string in the appsettings.js file.  Paste the following at the top of the appsettings.js file:

"DefaultConnection": "Data Source=localhost\\SQLEXPRESS; Initial Catalog=api4; Integrated Security=True; MultipleActiveResultSets=True;",

Now, we need to associate this connection string with our database.  Replace startup.cs with the following:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using api.Models;

namespace api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
            {
                options.Password.RequireNonAlphanumeric = false;
            })
            .AddEntityFrameworkStores<ApiDbContext>()
            .AddDefaultTokenProviders();

            var efConnection = Configuration["DefaultConnection"];
            services.AddDbContext<ApiDbContext>(options => options.UseSqlServer(efConnection));

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseMvc();
        }
    }
}

This changes the “ConfigureServices” function (adds identity support and sets our Entity Framework connection string), adds a few using statements at the top of the code.

Next, we need to setup ASP.NET core to run our seed.   Replace Program.cs with the following:


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using api.Models;

namespace api
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = BuildWebHost(args);

            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var userManager = services.GetRequiredService<UserManager<ApplicationUser>> ();
                    var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
                    ApiDbSeedData.Seed(userManager, roleManager).Wait();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred while seeding the database.");
                }
            }
            host.Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }
}

This will run the seed when the program starts up.

Now, let’s setup migrations.  Migrations tracks changes to the database and makes the changes when necessary.  Start off by opening the Package Manager Console but going to Tools -> Nuget Package Manager -> Package Manager Console.

  1. Type in: “Add-Migration” and type in “Initial” for the name.  This will create the first migration for the system.
  2. type in “Update-Database”.  This will create the initial database with our current tables.

The database is ready.

Middleware

Asp.net Core allows the user to create middleware to handle authentication.  We’ll setup the middleware to setup Authenticating and creating JWT’s.

Under Api, create a new folder and name it “Providers”.  Under the Providers folder create a new file and name it “TokenProviderMiddleWare”.  Paste in the following code:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using api.Models;
using api.Utils;

namespace api.Providers
{
    public class TokenProviderMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly JsonSerializerSettings _serializerSettings;

        public TokenProviderMiddleware(
            RequestDelegate next)
        {
            _next = next;
            _serializerSettings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented
            };
        }

        public Task Invoke(HttpContext context)
        {
            // If the request path doesn't match, skip
            if (!context.Request.Path.Equals("/api/token", StringComparison.Ordinal))
            {
                return _next(context);
            }

            // Request must be POST with Content-Type: application/x-www-form-urlencoded
            if (!context.Request.Method.Equals("POST")
               || !context.Request.HasFormContentType)
            {
                context.Response.StatusCode = 400;
                return context.Response.WriteAsync("Bad request.");
            }

            return GenerateToken(context);
        }

        private async Task GenerateToken(HttpContext context)
        {
            try
            {
                var username = context.Request.Form["username"].ToString();
                var password = context.Request.Form["password"];

                var signInManager = context.RequestServices.GetService<SignInManager<ApplicationUser>>();
                var userManager = context.RequestServices.GetService<UserManager<ApplicationUser>>();

                var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false);
                if (!result.Succeeded)
                {
                    context.Response.StatusCode = 400;
                    await context.Response.WriteAsync("Invalid username or password.");
                    return;
                }
                var user = await userManager.Users.SingleAsync(i => i.UserName == username);
                if (!user.IsEnabled)
                {
                    context.Response.StatusCode = 400;
                    await context.Response.WriteAsync("Invalid username or password.");
                    return;
                }
                var db = context.RequestServices.GetService<ApiDbContext>();
                var response = GetLoginToken.Execute(user, db);

                // Serialize and return the response
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));
            }
            catch (Exception ex)
            {
                //TODO log error
                //Logging.GetLogger("Login").Error("Erorr logging in", ex);
            }
        }

    }

}

In the “Invoke” function, this verifies the end point is “api/token” and then it verifies that it is a POST call.

In the “GenerateToken” function, it verifies the user logs in properly, creates the token and sets it.  The function “GetLoginToken.Execute” creates the actual token and is created next along with a few helper classes.

Under the “Utils” folder, create a class named “LoginResponseData” and paste the following:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace api.Utils
{
    public class LoginResponseData
    {
        public string access_token { get; set; }
        public string refresh_token { get; set; }
        public int expires_in { get; set; }
        public string userName { get; set; }
        public string firstName { get; set; }
        public string lastName { get; set; }
        public bool isAdmin { get; set; }
    }
}

Under the “Utils” folder, create a class named “Configuration” and paste the following:

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace api.Utils
{
    public static class Configuration
    {
        public static IConfigurationRoot Config { get; set; }

        static Configuration()
        {
            var builder = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json");
            Config = builder.Build();

            Configuration.Config = Config;
        }

        public static string DbConnection => Config["DefaultConnection"];
    }
}

This sets up the Configuration so we can use it to grab appsettings, and it sets up the database connection.

Under the “Providers” folder, create a file named “TokenProviderOptions” and paste in the following:

using Microsoft.IdentityModel.Tokens;
using System;

namespace api.Providers
{
    public class TokenProviderOptions
    {
        /// The relative request path to listen on.
        /// </summary>

        /// <remarks>The default path is <c>/token</c>.</remarks>
        public string Path { get; set; } = "api/token";

        ///  The Issuer (iss) claim for generated tokens.
        /// </summary>

        public string Issuer { get; set; }

        /// The Audience (aud) claim for the generated tokens.
        /// </summary>

        public string Audience { get; set; }

        /// The expiration time for the generated tokens.
        /// </summary>

        /// <remarks>The default is five minutes (300 seconds).</remarks>
        public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(60);

        /// The signing key to use when generating tokens.
        /// </summary>

        public SigningCredentials SigningCredentials { get; set; }

    }
}

These are the options that we will use when creating the JWT token.

Under the “Utils” folder, create a class named “GetLoginToken” and paste the following:


using api.Models;
using api.Providers;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
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 System.Threading.Tasks;

namespace api.Utils
{
    public class GetLoginToken
    {
        public static TokenProviderOptions GetOptions()
        {
            var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.Config.GetSection("TokenAuthentication:SecretKey").Value));

            return new TokenProviderOptions
            {
                Path = Configuration.Config.GetSection("TokenAuthentication:TokenPath").Value,
                Audience = Configuration.Config.GetSection("TokenAuthentication:Audience").Value,
                Issuer = Configuration.Config.GetSection("TokenAuthentication:Issuer").Value,
                Expiration = TimeSpan.FromMinutes(Convert.ToInt32(Configuration.Config.GetSection("TokenAuthentication:ExpirationMinutes").Value)),
                SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)
            };
        }

        public static LoginResponseData Execute(ApplicationUser user, ApiDbContext db)
        {
            var options = GetOptions();
            var now = DateTime.UtcNow;

            var claims = new List<Claim>()
            {
                new Claim(JwtRegisteredClaimNames.NameId, user.Id),
                new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUniversalTime().ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
                new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
            };

            var userClaims = db.UserClaims.Where(i => i.UserId == user.Id);
            foreach (var userClaim in userClaims)
            {
                claims.Add(new Claim(userClaim.ClaimType, userClaim.ClaimValue));
            }
            var userRoles = db.UserRoles.Where(i => i.UserId == user.Id);
            foreach(var userRole in userRoles)
            {
                var role = db.Roles.Single(i => i.Id == userRole.RoleId);
                claims.Add(new Claim(Extensions.RoleClaimType, role.Name));
            }

            var jwt = new JwtSecurityToken(
                issuer: options.Issuer,
                audience: options.Audience,
                claims: claims.ToArray(),
                notBefore: now,
                expires: now.Add(options.Expiration),
                signingCredentials: options.SigningCredentials);
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            var response = new LoginResponseData
            {
                access_token = encodedJwt,
                expires_in = (int)options.Expiration.TotalSeconds,
                userName = user.UserName,
                firstName = user.FirstName,
                lastName = user.LastName,
                isAdmin = claims.Any(i => i.Type == Extensions.RoleClaimType && i.Value == Extensions.AdminRole)
            };
            return response;
        }
    }

}

This creates the token provider options from appsettings, and sets the login response (setting the encoded JWT).   This also sets the user’s claims and roles.  This also adds a few fields to the appsettings.js file.  Let’s just replace it with the following:

{
  "DefaultConnection": "Data Source=localhost\\SQLEXPRESS; Initial Catalog=api4; Integrated Security=True; MultipleActiveResultSets=True;",
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "TokenAuthentication": {
    "SecretKey": "kW9ys7uuoUfiY8pQyBke7WMhZ2DhuyntGPCVPySuSvc",
    "Issuer": "http://localhost",
    "Audience": "e6b0f93b602544089d3436fabc5b4ab0",
    "ExpirationMinutes": "60",
    "CookieName": "access_token"
  }

}

This is where JWT details are retrieved from.   Secret Key, Issuer, Audience, and ExpirationMinutes are all used for the token.  You can set these to the appropriate settings or generate them yourself.  (In my first talk about JWT authentication, I created a unit test that creates the Secret Key and Issuer.)

Bringing It All Together

Now, we have all of the components to perform token base authentication, we just need to wire it up.  Let’s replace the current startup.cs file with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.EntityFrameworkCore;
using api.Models;
using api.Providers;

namespace api
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy", builder =>
                {
                    builder.AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowAnyOrigin()
                    .AllowCredentials();
                });
            });

            services.AddMvc(options =>
            {
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("UserManagement", policy => policy.RequireClaim("manage_user"));
                options.AddPolicy("Admin", policy => policy.RequireClaim("admin"));
                options.AddPolicy("User", policy => policy.RequireClaim("user"));
            });

            services.AddIdentity<ApplicationUser, IdentityRole>(options =>
            {
                options.Password.RequireNonAlphanumeric = false;
            })
            .AddEntityFrameworkStores<ApiDbContext>()
            .AddDefaultTokenProviders();

            var efConnection = Configuration["DefaultConnection"];
            services.AddDbContext<ApiDbContext>(options => options.UseSqlServer(efConnection));

            // return 401 instead of redirect to login
            services.ConfigureApplicationCookie(options => {
                options.Events.OnRedirectToLogin = context => {
                    context.Response.Headers["Location"] = context.RedirectUri;
                    context.Response.StatusCode = 401;
                    return Task.CompletedTask;
                };
            });

            services.AddAuthentication(sharedOptions =>
            {
                sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
                      .AddJwtBearer(cfg =>
                      {
                          cfg.RequireHttpsMetadata = false;
                          //cfg.SaveToken = true;

                          cfg.TokenValidationParameters = new TokenValidationParameters()
                          {
                              ValidateIssuer = true,
                              ValidateAudience = true,
                              ValidateLifetime = true,
                              ValidateIssuerSigningKey = true,
                              ValidIssuer = Configuration["TokenAuthentication:Issuer"],
                              ValidAudience = Configuration["TokenAuthentication:Audience"],
                              IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["TokenAuthentication:SecretKey"]))
                          };

                          cfg.Events = new JwtBearerEvents
                          {
                              OnAuthenticationFailed = context =>
                              {
                                  Console.WriteLine("OnAuthenticationFailed: " +
                                      context.Exception.Message);
                                  return Task.CompletedTask;
                              },
                              OnTokenValidated = context =>
                              {
                                  Console.WriteLine("OnTokenValidated: " +
                                      context.SecurityToken);
                                  return Task.CompletedTask;
                              }
                          };

                      });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        // NOTE: DI is done here
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,
            ApiDbContext context, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseCors("CorsPolicy");

            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            ApiDbSeedData.Seed(userManager, roleManager).Wait();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMiddleware<TokenProviderMiddleware>();
            app.UseAuthentication();

            app.UseMvc();
        }

    }
}<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

This sets up the system:

  • lines 33-42 setup the CORS policy so any client can connect.
  • lines 48-43 setup some basic policies (for authentication, more on that later)
  • lines 55-60 setup Identity with our ApplicationUser and ApiDbContext
  • lines 55-56 setup the connection string for the ApiDbContext
  • lines 66-73 make the system return a 401 if the user is not authenticated (normally, it would try to route the user to a login screen, but we don’t want that.)
  • lines 74-111 setup the JWT authentication
  • line 126 tells the system to user our cors policy
  • line 139 sets up our token middleware

Testing

For testing, I use chrome and postman.

  1. Execute the api (F5 or Debug -> Start Debugging)
  2. In Chrome, open postmapostman-1.PNG
  3. Setup the URL to point to api/token (your port may be different)
  4. Set to perform a POST rest call
  5. set to use x-www-form-urlencoded
  6. the the username to admin and password to Admin1234!
  7. click send

If all works correct, you’ll get the access token along with a few other items:

  • refresh_token: this will be used in my next post
  • expires_in: seconds until token expiration
  • username: the username
  • firstname / lastname: should be obvious
  • isAdmin: if the user is an admin (i.e. in admin claim)

Now we can use this token to access restricted data.  The system should’ve setup a controller (under controllers) named “ValuesController”.  If not, then add one.  Let’s also add the “Authorize” attribute so it must be authenticated to work:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

namespace api.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

Now, rerun the application and open postman.   If you try to access the values controller endpoint without providing a token, you’ll receive a 401 (unauthorized):

postman-2.PNG

But if you add the token that you received when logging in, it’ll return data:

postman-3.PNG

Success!   We successfully generated a JWT which we can use to authorize through the system.   Furthermore, I setup policies and roles and the current user is in the Admin Policy and Admin Role so if you change the authorize attribute to [Authorize(Policy=”admin”)]  (or if you want to use roles then do: [Authorize(Roles=”admin”)]) then the user should still be able to authenticate against that policy.  If you set the policy (or role) to “User” then we should get a 403 error.

My next talk will explain how to implement refresh tokens in asp.net core 2.0.  This can be found here.

2 thoughts on “Token Based Authentication Using Asp.Net Core 2.0

  1. I need to issue a refresh token. I’m porting my existing web api from Asp.Net 4 and my token provider issues refresh tokens. Any guidance on how to do this in asp.net core 2?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s