Description:
This code example shows how to build a Web API (Restful Service) into a web page, secure access and transfer with jwtToken Java Web Tokens, and read and edit the data with a UWP client.
terms:
UWP Universal Windows Platform app
JWT Java Web Token
API application programming interface
C # code
Step 1: Requirements
Asp.Net Core: Insert Api Autorize with Jwt Tokens
Jwt = Java Web Tokens
requirements:
You have to insert the Microsoft NuGet Package System IdentityModel Tokens JWT
To do this, open the Asp Solution-> Contect menu-> Manage Nuget Packages
Then enter Browse-> jwt
System.IdentityModel.Tokens.Jwt
Includes types that provide support for creating, serializing and validating JSON Web Tokens.
Then under Asp.Net Core Project-> Dependencies-> NuGet
The entry: System.IdentityModel.Tokens.Jwt stand
And in addition the Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Authentication.JwtBearer
ASP.NET Core bearer token to OpenID Connect bearer token.
Step 2: Create Server TokenController
The data communication is protected by a backup token. To do this, the client app must first pick up a security token and pass it on as authorization in subsequent transfers.
The security token can be picked up in an Asp.Net controller.
The security token contains a key and a validity date known only to the server.
The security token becomes a user token by incorporating additional information into the token as claims. Here the user is entered as claim.
The claims can then be evaluated when inquiring about data and again checked against the user.
Through this additional information Claim.User in the token is also later guaranteed that each user can only read and write his own data.
Token controller
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc;
//< using > using System.Text; //*Encoding using Microsoft.IdentityModel.Tokens; //*SymmetricSecurityKey using System.Security.Claims; //*Claims for JWT Token using System.IdentityModel.Tokens.Jwt; //*JwtRegisteredClaimNames using Microsoft.Extensions.Primitives; //StringValues //</ using >
namespace Freelance.Controllers.api { //*when open an api-Connection, first call /api/token and get a valid token to work with the api data [Produces("application/json")] public class TokenController : Controller { //--------------< Class: TokenController >--------------------- //*min 16 chars SymmetricSecurityKey _secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Demo_SecretKey_2018-06-27"));
[Route("api/get_InitToken")] [HttpGet] public IActionResult Get_Init_Token() { //--------< Get_Init_Token >-------- //--< Create a Token >-- JwtSecurityToken jwtInitToken = new JwtSecurityToken( issuer: "website_freelancer", //ASP.NET Core web application audience: "webclients_freelancer", //client app notBefore: DateTime.Now, expires: DateTime.Now.AddDays(1), signingCredentials: new SigningCredentials(_secretKey, SecurityAlgorithms.HmacSha256) ); //--</ Create a Token >--
string stringToken = new JwtSecurityTokenHandler().WriteToken(jwtInitToken);
return Ok(stringToken); //--------</ Get_Init_Token() >-------- }
[Route("api/get_usertoken")] [HttpGet] public IActionResult Get_UserToken() { //-------------< Get_UserToken() >------------- //*Create a Usertoken if parameters are correct.
string username = ""; string password = "";
if (Request.Headers.TryGetValue("Authorization", out StringValues authToken)) { string authHeader = authToken.First(); string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim(); Encoding encoding = Encoding.UTF8; string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword)); int seperatorIndex = usernamePassword.IndexOf(':'); username = usernamePassword.Substring(0, seperatorIndex); password = usernamePassword.Substring(seperatorIndex + 1); } else { return BadRequest("Missing Authorization Header."); }
//*check here against user and password if (check_login_user_password(username,password) == true) { //< login ok > string sToken = create_UserToken(username); return Content(sToken); //</ login ok > } else { //< login failed > return BadRequest(); //</ login failed > }
//-------------</ Get_UserToken() >------------- }
//*reference: www.blinkingcaret.com public string create_UserToken(string sUsername) { //-------------< create_UserToken() >------------- Claim[] claims = new Claim[] { new Claim(ClaimTypes.Name, sUsername), //*->User.Identity.Name or "Raimund Popp" new Claim(JwtRegisteredClaimNames.Email, "raimund.popp@codedocu.de")
};
//--< Create a Token >-- JwtSecurityToken jwtToken = new JwtSecurityToken( issuer: "website_freelancer", //ASP.NET Core web application audience: "webclients_freelancer", //client app claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddDays(1), signingCredentials: new SigningCredentials(_secretKey, SecurityAlgorithms.HmacSha256) ); //--</ Create a Token >--
string stringToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
return stringToken; //-------------</ create_UserToken() >------------- }
private bool check_login_user_password(string sUsername, string sPassword) { //-------------< check_login_user_password() >------------- //#todo: check user and password from user. return true; //-------------</ check_login_user_password() >------------- } //--------------</ Class: TokenController >--------------------- } }
|
Step 3: Authorize WebApi
Web API Controller
If the client later wants to fetch or write its data, it will switch to the WebApi controller.
This WebApi controller is usually located in a subdirectory / Api.
At the beginning of the controller call, access to the WebApi Controller is checked by preceding the authorization.
For this, the attribute: Authorize with the schema JwtBearer is to be inserted
[Authorize(AuthenticationSchemes = "JwtBearer")] |
WebApi Controller: Api / ProjectsController
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Freelance.Data; using Freelance.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc;
namespace Freelance.Controllers.api { [Authorize(AuthenticationSchemes = "JwtBearer")] //*goes to startup ConfigureServices.AddAuthentication->AddJwtBearer(..) [Produces("application/json")] [Route("api/Projects")] public class ProjectsController : Controller { //--------------< Class: ApiController >--------------------- #region Controller Init private readonly ApplicationDbContext _dbContext;
public ProjectsController(ApplicationDbContext dbContext) { //----< Init: Controller >---- _dbContext = dbContext; //----</ Init: Controller >---- } #endregion
// GET: /api/index //[Authorize] public List<ProjectModel> Index() { ///-------------< Index >-------------
//--< Get Linq.Query >-- //*gets last 10 Projects with View_Sum var query = (from n in _dbContext.tbl_Projects where n.IsDraft == false orderby n.IDProject descending select n ).Take(10); //--</ Get Linq.Query >--
//----< fill Data_to_View >---- List<ProjectModel> dataList = query.ToList<ProjectModel>();
//< out > //*output to client return dataList; //</ out > ///-------------</ Index >------------- } //--------------</ Class: ApiController >--------------------- } }
|
Step 5: Insert Authorization Service
If a client application wants to open a Wep API (REST Service), then the attribute: Authorize in the controller will check.
[Authorize(AuthenticationSchemes = "JwtBearer")] |
The exam must be inserted as a schema in the Asp.Net Core Startup file in the Configure Services area
//----< JWT-Token >---- //*reference: www.blinkingcaret.com services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; })
.AddJwtBearer("JwtBearer", jwtBearerOptions => { jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Demo_SecretKey_2018-06-27")),
ValidateIssuer = true, ValidIssuer = "website_freelancer",
ValidateAudience = true, ValidAudience = "webclients_freelancer",
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; }); //----</ JWT-Token >---- |
As well as in Startup Block Configure the authorization has to be activated in general
app.UseAuthentication(); |
Startup.cs
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Freelance.Data; using Freelance.Models; using Freelance.Services; using Microsoft.AspNetCore.Rewrite; using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; //*TokenValidationParameters using System.Text; //*Encoding
namespace Freelance { public class Startup {
public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder(); builder.AddUserSecrets<Startup>();
Configuration = builder.Build(); }
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) { //-----------< ConfigureServices() >----------- //Website_Constants.Connectionstring = Configuration["DefaultConnection"].ToString();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Website_Constants.Connectionstring)); //--< Identity >-- services.AddIdentity<ApplicationUser, IdentityRole>(config => { //< send Register Email > //*prevents registered users from logging in until their email is confirmed. config.SignIn.RequireConfirmedEmail = true; //</ send Register Email > }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); //--</ Identity >--
//----< JWT-Token >---- //*reference: www.blinkingcaret.com services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "JwtBearer"; options.DefaultChallengeScheme = "JwtBearer"; })
.AddJwtBearer("JwtBearer", jwtBearerOptions => { jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Demo_SecretKey_2018-06-27")),
ValidateIssuer = true, ValidIssuer = "website_freelancer",
ValidateAudience = true, ValidAudience = "webclients_freelancer",
ValidateLifetime = true, //validate the expiration and not before values in the token
ClockSkew = TimeSpan.FromMinutes(5) //5 minute tolerance for the expiration date }; }); //----</ JWT-Token >----
// Add application services. services.AddTransient<IEmailSender, EmailSender>();
var optRewrite = new RewriteOptions() .AddRedirectToHttpsPermanent();
services.AddMvc();
//-----------</ ConfigureServices() >----------- }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //-----------< Configure() >----------- if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler("/Home/Error"); }
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes => {
//--< Emoticons >-- routes.MapRoute( name: "🏠", template: "🏠", defaults: new { controller = "Home", action = "Index" } ); routes.MapRoute( name: "📢", template: "📢", defaults: new { controller = "Home", action = "Index" } ); routes.MapRoute( name: "📜", template: "📜", defaults: new { controller = "Projects", action = "Index_all" } ); //--</ Emoticons >--
routes.MapRoute( name: "Projects", // Route name template: "Projects", // URL with parameters defaults: new { controller = "Projects", action = "Index_all" } );
routes.MapRoute( name: "default", template: "{controller=Projects}/{action=Index_all}/{id?}");
} );
//seed dbContext Database.EF_Model.Initialize_DbContext_in_Startup(app.ApplicationServices); //-----------</ Configure() >----------- } } }
|
Step 6: Client access
Here the example of a UWP client
Client: UWP client
The client fetches a user token in the first step. It receives this token from the server under / api / get_usertoken.
For this purpose, you can initially transfer the user and password so that the user is entered as claim information in the UserToken.
//-< get user token >- //*get the User-Password valid token string sURL_Login = "http://localhost:51198/api/get_usertoken"; var byteArray = Encoding.UTF8.GetBytes("MyUSER:MyPASS"); var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); client.DefaultRequestHeaders.Authorization = header;
string sUserToken = await client.GetStringAsync(sURL_Login); //-< get user token >-
|
Then the client can use this UserToken to fetch, read and edit their own data with the Web API Controller (REST Services).
//< read webApi > string sURL = "http://localhost:51198/api/projects"; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sUserToken); string sResponse_Projects = ""; try { sResponse_Projects = await client.GetStringAsync(sURL); } catch (Exception ex) { string sMessage = ex.InnerException.ToString(); MessageDialog dialog = new MessageDialog(sMessage, "Information"); await dialog.ShowAsync(); return; } //</ read webApi >
|
Client code
using System; using System.Net; using System.Net.Http; //*HttpClient using System.Net.Http.Headers; //*AuthenticationHeaderValue using System.Text; //*Encoding using Windows.Data.Json; //*JsonObject using Windows.UI.Popups; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls;
namespace api01 {
public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); }
private void btnStart_Click(object sender, RoutedEventArgs e) { read_API_Data();
}
public async void read_API_Data() { //---------------< read_API_Data() >---------------
HttpClient client = new HttpClient();
//-< get init token >- //*plain init-Token //string sURL_Login = "http://localhost:51198/api/get_InitToken"; //string sToken = await client.GetStringAsync(sURL_Login); //-</ get init token >-
//-< get user token >- //*get the User-Password valid token string sURL_Login = "http://localhost:51198/api/get_usertoken"; var byteArray = Encoding.UTF8.GetBytes("MyUSER:MyPASS"); var header = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); client.DefaultRequestHeaders.Authorization = header;
string sUserToken = await client.GetStringAsync(sURL_Login); //-< get user token >-
//< read webApi > string sURL = "http://localhost:51198/api/projects"; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", sUserToken); string sResponse_Projects = ""; try { sResponse_Projects = await client.GetStringAsync(sURL); } catch (Exception ex) { string sMessage = ex.InnerException.ToString(); MessageDialog dialog = new MessageDialog(sMessage, "Information"); await dialog.ShowAsync(); return; } //</ read webApi >
//< get Json values > JsonArray jsonArray = JsonArray.Parse(sResponse_Projects); foreach (var jsonRow in jsonArray) { //----< json Row >---- JsonObject jsonObject=jsonRow.GetObject();
//< values > string IDProject= jsonObject["idProject"].ToString(); string Title = jsonObject["title"].ToString(); string Text = jsonObject["text"].ToString(); string dtEdit = jsonObject["dtEdit"].ToString(); //</ values >
tbxResults.Text += Environment.NewLine + "-------"; tbxResults.Text += Environment.NewLine + "idProject=" + IDProject; tbxResults.Text += Environment.NewLine + "Title=" + Title; tbxResults.Text += Environment.NewLine + "Text=" + Text; tbxResults.Text += Environment.NewLine + "dtEdit=" + dtEdit; //----</ json Row >---- }
//</ get Json values > //---------------</ read_API_Data() >--------------- }
} }
|