Code-Beispiel: Asp.Net Core Web-API erstellen und mit UWP Client App Daten austauschen unter Verwendung von jwtToken
Beschreibung:
Dieses Code-Beispiel zeigt, wie man in eine Webseite eine Web API (Restful Service) einbaut, mit jwtToken Java Web Tokens den Zugriff und die Übertragung absichert und mit einem UWP Client die Daten liest und bearbeitet.
Begriffe:
UWP Universal Windows Plattform app
JWT Java Web Token
API Application Programming Interface
C# Code
Schritt 1: Voraussetzungen
Asp.Net Core: Api Autorize mit Jwt-Tokens einfügen
Jwt=Java Web Tokens
Vorraussetzungen:
Man muss das Microsoft NuGet Package System IdentityModel Tokens JWT einfügen
Hierzu unter der Asp Solution->Contekt-Menü->Manage Nuget Packages öffnen
Dann in Browse->jwt eingeben
System.IdentityModel.Tokens.Jwt
Includes types that provide support for creating, serializing and validating JSON Web Tokens.
Danach sollte unter Asp.Net Core Project->Dependencies->NuGet
Der Eintrag: System.IdentityModel.Tokens.Jwt stehen
Und zusätzlich die Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Authentication.JwtBearer
ASP.NET Core middleware that enables an application to receive an OpenID Connect bearer token.
Schritt 2: Server TokenController erstellen
Die Datenkommunikation wird über einen Sicherungstoken abgesichert. Hierzu muss die Client-App zunächst einen Sicherheitstoken abholen und diesen in den folgenden Übertragungen als Authorisierung mitübergeben.
Der Sicherheitstoken kann in einem Asp.Net Controller abgeholt werden.
Der Sicherheitstoken enthält einen Schlüssel und ein Gültigkeitsdatum, welcher nur dem Server bekannt ist.
Der Sicherheitstoken wird zu einem UserToken, indem in den Token zusätzliche Informationen eingebaut werden als Claims. Hier wird der User als Claim eingetragen.
Die Claims können dann beim Anfragen von Daten ausgewertet werden und erneut gegen den User geprüft werden.
Durch diese Zusatzinformation Claim.User im Token wird auch später garantiert, dass jeder User nur seine eigenen Daten lesen und schreiben kann.
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 >--------------------- } }
|
Schritt 3: WebApi Authorisieren
Web API Controller
Wenn der Client später seine Daten abholen oder schreiben möchte, dann wechselt dieser zum WebApi-Controller.
Dieser WebApi Controller befindet sich in der Regel in einem Unterverzeichnis /Api.
Zu Beginn des Controller-Aufrufs wird der Zugriff auf den WebApi Controller geprüft, indem die Authorizierung vorgeschaltet wird.
Hierzu ist das Attribute: Authorize mit dem Schema JwtBearer einzufügen
[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 >--------------------- } } |
Schritt 5: Authorisierung Service einfügen
Wenn eine Client Anwendung eine Wep API (REST Service) öffnen möchte, dann führt das Attribute: Authorize im Controller eine Prüfung durch.
[Authorize(AuthenticationSchemes = "JwtBearer")] |
Die Prüfung muss als Schema in der Asp.Net Core Startup Datei eingefügt werden im Bereich Configure Services
//----< 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 >---- |
Sowie im Startup Block Configure muss die Authorisierung generell aktiveriert werden
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() >----------- } } }
|
Schritt 6: Client Zugriff
Hier am Beispiel eines UWP Clients
Client: UWP Client
Der Client holt sich im ersten Schritt einen User-Token ab. Diesen Token erhält er vom Server unter /api/get_usertoken.
Hierzu kann man den User und Passwort initial übertragen, damit in den UserToken der User als Claim-Information eingetragen wird.
//-< 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 >- |
Anschliessend kann der Client mit diesem UserToken seine eigenen Daten mit dem Web API Controller (REST Services) holen, lesen und bearbeiten.
//< 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() >--------------- }
} } |