How to get jwt token from NodeJs to .NET app? - node.js

What is the best way to get a jwt token from a running NodeJS server, in a C# .NET Windows app?
in .NET I use HttpClient to connect to an oauth2 server (and that succeeds), but how to get the very jwt token?
In NodeJS:
const express = require('express')
const https = require('https');
const axios = require('axios');
var url = require('url');
const app = express()
const port = 3000
const agent = new https.Agent({ rejectUnauthorized: false });
async function get_token() {
try {
let url = "https://oauthservername/token.oauth2";
let formfields = "client_id=cid&grant_type=password&validator_id=ourAuthNG&client_secret=secretstring&username=billy&password=xxxxxxxxx";
let response = await axios.post(url, formfields, { httpsAgent: agent });
console.log(response.data);
} catch (error) {
console.log(error);
}
}
app.get('/token', (req, res) => {
get_token();
res.send('Eureka!');
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
This is working. I am getting the "Eureka!", in Postman as well as in my .NET HttpClient call
In my console I am getting (x-ing original info...), (output from console.log(response.data);)
{
access_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
refresh_token: 'xxxxxxxxxxxxxxxxx',
token_type: 'Bearer',
expires_in: 28799
}
So, in my C# code I do this (also getting "Eureka!", I see it in the responseBody):
private async void cmdConnectServer_Click(object sender, EventArgs e)
{
//client is HttpClient
client.BaseAddress = new Uri("http://localhost:3000/");
HttpResponseMessage response = await client.GetAsync("http://localhost:3000/token");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
}
But how to get the very token?

In NodeJS you never send the token result in the api response, modify it like this :
async function get_token() {
try {
let url = "https://oauthservername/token.oauth2";
let formfields = "client_id=cid&grant_type=password&validator_id=ourAuthNG&client_secret=secretstring&username=billy&password=xxxxxxxxx";
let response = await axios.post(url, formfields, { httpsAgent: agent });
console.log(response.data);
//Return the oauth2 result
return response.data;
} catch (error) {
console.log(error);
}
}
app.get('/token', (req, res) => {
//sending 'Eureka!' is pointless, instead send the token result
res.send(get_token());
})
In c#
using System;
using System.Runtime.Serialization;
using System.Text.Json;
... ...
//Class for response deserialization
[Serializable]
public class TokenResult
{
[DataMember]
public string access_token { get; set; }
[DataMember]
public string refresh_token { get; set; }
[DataMember]
public string token_type { get; set; }
[DataMember]
public long expires_in { get; set; }
}
private async void cmdConnectServer_Click(object sender, EventArgs e)
{
//client is HttpClient
client.BaseAddress = new Uri("http://localhost:3000/");
HttpResponseMessage response = await client.GetAsync("http://localhost:3000/token");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
// Deserialize the token response and get access_token property
string token = JsonSerializer.Deserialize<TokenResult>(responseBody ).access_token;
}
Or just don't use node js and do everything in c#
using System;
using System.Runtime.Serialization;
using System.Text.Json;
... ...
//Class for response deserialization
[Serializable]
public class TokenResult
{
[DataMember]
public string access_token { get; set; }
[DataMember]
public string refresh_token { get; set; }
[DataMember]
public string token_type { get; set; }
[DataMember]
public long expires_in { get; set; }
}
public string get_token()
{
HttpClientHandler OpenBarHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator };
HttpClient _httpClient = new HttpClient(OpenBarHandler);
_httpClient.BaseAddress = new Uri("https://oauthservername/token.oauth2");
HttpRequestMessage mess = new HttpRequestMessage();
mess.Method = HttpMethod.Post;
mess.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Dictionary<string, string> _parameters = new Dictionary<String, String>();
_parameters.Add("grant_type", "password");
_parameters.Add("username", "billy");
_parameters.Add("password", "xxxxxxxxx");
_parameters.Add("client_id", "cid");
_parameters.Add("client_secret", "secretstring");
_parameters.Add("validator_id", "ourAuthNG");
FormUrlEncodedContent encodedContent = new FormUrlEncodedContent(_parameters);
encodedContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
mess.Content = encodedContent;
HttpResponseMessage response = _httpClient.SendAsync(mess).Result;
if (response.IsSuccessStatusCode)
{
string strResp = response.Content.ReadAsStringAsync().Result;
return JsonSerializer.Deserialize<TokenResult>(strResp).access_token;
}
else
throw new Exception("Token retrieval failed");
}

Related

Cannot able to pass data from Node to asp.net MVC

On the button click event of React side I am calling a node backend.
click event of react,
// calling node backend
this.uploadApi.command(postData.bin_files, this.dummy);
this.setState({submit_form});
}
dummy = (result)=>{
console.log(result);
}
This is my Node backend code,
import axios from 'axios';
class UploadFile {
constructor() {
this.url = 'http://localhost:56246/microservice/uploaddata'; //This is the local MVC application's URL (microservice is the controller)
}
command(postData, callback, uploadCallback = null) {
let jsonDataString = JSON.stringify(postData).replace(/&/g, '--and--');
jsonDataString = jsonDataString.replace(/\+/g, '--plus--');
const payload = JSON.parse(jsonDataString);
console.log('----------');
console.log(this.url);
console.log(payload);
console.log('----------');
// var data = qs.stringify({'jsondata':payload});
const data = new FormData();
for (var i = 0; i < payload.length; i++) {
console.log('inside for 1');
data.append(`model[${i}].name`, payload[i].name);
data.append(`model[${i}].bin_file`, payload[i].bin_file);
console.log('inside for 2');
}
console.log('=============');
console.log(data);
console.log('=============');
var config = {
method: 'post',
url: this.url,
headers: {
'Content-Type': 'multipart/form-data'
},
data: "jsondata=" + data,
onUploadProgress: (progressEvent) => {
const {
loaded,
total
} = progressEvent;
console.log("loaded:", loaded);
console.log("total:", total);
if (uploadCallback !== null) uploadCallback(progressEvent);
}
};
axios(config)
.then(function(response) {
// console.log(JSON.stringify(response.data));
callback(response.data);
})
.catch(function(error) {
console.log(error);
});
// axios.post(this.url, data)
// .then(res => console.log(res.data))
// .catch((error) => { console.error(error) });
}
}
export default UploadFile;
And this is my respective controller,
public dynamic UploadData(List<MemberInfo> model)
{
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = mstrDBConStringNew;
conn.Open();
SqlCommand command = new SqlCommand("SELECT * from tempstorage", conn);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
//port = reader.GetString(0);
}
}
}
return "Hiiiiiiii";
}
public class MemberInfo
{
public string name { get; set; }
public string bin_file { get; set; }
}
Now If I show You while debugging, the controller and its respective action gets called but the value that I am expecting is null.
I have also tried like this way, but no luck
public dynamic UploadData(FormCollection model)
{
using (SqlConnection conn = new SqlConnection())
{
conn.ConnectionString = mstrDBConStringNew;
conn.Open();
SqlCommand command = new SqlCommand("SELECT * from tempstorage", conn);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
//port = reader.GetString(0);
}
}
}
return "Hiiiiiiii";
}
This is my network request,
Please ask if anything additional is needed.
Yes I was able to figure out the issue,
var config = {
method: 'post',
url: this.url,
headers: {
'Content-Type': 'multipart/form-data'
},
data: data, // previously it was, "jsondata=" + data
Here I am getting the data as expected..

How can i store the ID permanent? (Flutter)

I am developing an app with Flutter and Firebase.
I want to store the _id with SharedPreferences permanently.
Therefore, i looked after it, but my code doesnt work at all.
It always throws the error:
type 'Future' is not a subtype of type 'String'
Here is my code:
class Profile with ChangeNotifier {
String _id;
void setName(String name) {
const url =
'myurl';
http
.post(url, body: json.encode({'name': name, 'description': name}))
.then((response) {
_id = json.decode(response.body)['name'];
});
addID();
}
Future<void> updateName(String name, String id) async {
String url =
'myurl';
await http.patch(url,
body: json.encode({'name': 'Ein Titel', 'description': name}));
}
And here are my methods with the SharedPrefs:
String getID() {
return getIDOffline();
}
addID() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('id', _id);
}
getIDOffline() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
//Return String
String stringValue = prefs.getString('id');
return stringValue;
}
Try this:
void setName(String name) {
const url = 'myurl';
var body = json.encode({'name': name, 'description': name});
var response = await http.post(url, body: body);
String id = json.decode(response.body)['name'];
addID(id);
}
addID(id) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('id', id);
}
Future<String> getID() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
//Return String
String stringValue = prefs.getString('id');
return stringValue;
}
Your getID is async function so it return Future and not String..

How to get and set value using session in .net core 2.0 api

Instead of below list. I want to fetch the Username and Password from session
private List _users = new List
{
new User { Id = 1, FirstName = "Jeevan", LastName = "Nigade", Username = "Jeevan", Password = "jeevan" }
};
This is my controller Code :-
[Route("api")]
[ApiController]
public class UsersController : ControllerBase
{
private IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost("token")]
public IActionResult Authenticate([FromBody]User userParam)
{
//var user
try
{
if (string.IsNullOrEmpty(userParam.Username))
{
return StatusCode(400, "Username Cannot Be Null..!!");
}
else if (string.IsNullOrEmpty(userParam.Password))
{
return StatusCode(400, "Password Cannot Be Null..!!");
}
else
{
var user = _userService.Authenticate(userParam.Username, userParam.Password);
if (user == null)
{
return StatusCode(400,"Username or password is incorrect..!!");
}
return Ok(user);
}
}
catch(Exception ex)
{
return StatusCode(500, ex.Message);
}
}
[Authorize]
[HttpGet("private")]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
}
Below is my class UserServie.cs code :-
public class UserService : IUserService
{
private List<User> _users = new List<User>
{
new User { Id = 1, FirstName = "Jeevan", LastName = "Nigade", Username = "Jeevan", Password = "jeevan" }
};
private readonly AppSettings _appSettings;
public UserService(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public User Authenticate(string username, string password)
{
var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
if (user == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var ClientValues = _appSettings.ClientId + _appSettings.ClientSecret;
var key = Encoding.ASCII.GetBytes(ClientValues);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
//Expires = DateTime.UtcNow.AddSeconds(10),
Expires = DateTime.Now.AddSeconds(10),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
user.Password = null;
return user;
}
public IEnumerable<User> GetAll()
{
return _users.Select(x => {
x.Password = null;
return x;
});
}
}
where I have set the list value.
In this where and how can i set the Session value and how do i fetch it.
From your code,the user list seems to be all the users.I suggest that you could get them like below:
var user = _context.User.ToList();
For you want to set session and get it.Here is a working demo like below:
1.Controller.cs
public class UsersController : ControllerBase
{
private IUserService _userService;
public const string SessionKeyName = "user";
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPost("token")]
public IActionResult Authenticate([FromBody]User userParam)
{
var users = new User { Id = 1, FirstName = "Jeevan", LastName = "Nigade", Username = "Jeevan", Password = "jeevan" };
HttpContext.Session.SetComplexData(SessionKeyName, users);
var _users = HttpContext.Session.GetComplexData<User>(SessionKeyName);
try
{
if (string.IsNullOrEmpty(userParam.Username))
{
return StatusCode(400, "Username Cannot Be Null..!!");
}
else if (string.IsNullOrEmpty(userParam.Password))
{
return StatusCode(400, "Password Cannot Be Null..!!");
}
else
{
var user = _userService.Authenticate(userParam.Username, userParam.Password,_users);
if (user == null)
{
return StatusCode(400, "Username or password is incorrect..!!");
}
return Ok(user);
}
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
}
[Authorize]
[HttpGet("private")]
public IActionResult GetAll()
{
var _users = HttpContext.Session.GetComplexData<User>(SessionKeyName);
var users = _userService.GetAll(_users);
return Ok(users);
}
2.UserService:
public class UserService : IUserService
{
public User Authenticate(string username, string password,User _users)
{
if (_users.Username == username && _users.Password == password)
return _users;
return null;
}
public User GetAll(User _users)
{
return _users;
}
}
3.IUserService:
public interface IUserService
{
User Authenticate(string username, string password,User _users);
User GetAll(User _users);
}
4.SessionExtensions:
public static class SessionExtensions
{
public static T GetComplexData<T>(this ISession session, string key)
{
var data = session.GetString(key);
if (data == null)
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(data);
}
public static void SetComplexData(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
}
5.Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IUserService, UserService>();
services.AddSession();
}
// 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.UseSession();
app.UseMvc();
}

How to check if record exists in Azure table with Asp.Net Identity and return a custom response

My Xamarin.Forms mobile app uses a Web Api and Asp.Net Identity to register the user and store the information in a default Azure table called dbo.AspNetUsers. I would like to check whether the record exists before registering the user and return a relevant message if the user already exists (email should be unique in this case).
In my Web Api:
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, Region = model.Region };
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
In my Xamarin.Forms PCL:
public class RegisterBindingModel
{
public string Email { get; set; }
public string Region { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
}
public async Task<bool> RegisterAsync (string email, string password, string confirmPassword, string firstName, string lastName, string region)
{
var client = new HttpClient();
// create object to send to web api for registering
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword,
FirstName = firstName,
LastName = lastName,
Region = region
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
// add for API Key
content.Headers.Add(AppConstants.API_KEY_NAME, AppConstants.API_KEY_VALUE);
var response = await client.PostAsync(AppConstants.AZURE_WEB_API_REGISTER_URL, content);
return response.IsSuccessStatusCode;
}
a cursory glance at the docs for UserManager shows a FindByNameAsync method
Thanks #Jason for the help. It was easy in the end. This is how I solved it:
[Route("Register")]
public async Task<IHttpActionResult> Register(RegisterBindingModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var user = new ApplicationUser() { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, Region = model.Region };
var existingUser = await UserManager.FindByEmailAsync(user.Email);
if (existingUser == null)
{
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (!result.Succeeded)
{
return GetErrorResult(result);
}
return Ok();
}
else
{
var errorModel = new
{
error = new
{
code = 400,
message = "Email already exists"
}
};
return Content(HttpStatusCode.BadRequest, errorModel);
}
}

Asp.Net Core 1.0 authentication migrating to 2.0

I have a web API that I want to migrate to Asp.Net Core 2.0. The API is secured and I want to migrate it to 2.0, because we finished first circle. I tried something, but when I protect my controller with the [Authenticate] attribute, the controller at the given endpoint never get called, because the user is not authenticated.
public partial class Startup
{
public IConfigurationRoot Configuration { get; set; }
private static JwtOptions _jwtOptions;
private readonly IUserService _userService = new UserService();
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Add DI and other services
SetServices(services);
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedProto;
});
services.AddAuthentication(scheme =>
{
scheme.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "aaa",
ValidAudience = "bbb",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secret key"))
};
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(365);
options.Events = new CustomCookieAuthenticationEvents();
options.Cookie.Name = "access_token";
});
services.Configure<CookieAuthenticationOptions>(options =>
{
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents()
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == 200)
{
ctx.Response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized;
return Task.FromResult<object>(null);
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
return Task.FromResult<object>(null);
}
}
};
});
//Logger
services.AddMvc(options =>
{
options.Filters.Add(new Loging.ApiExceptionFilter());
});
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
});
// Add framework services.
MvcOptions mvcOptions = new MvcOptions();
mvcOptions.Filters.Add(new RequireHttpsAttribute());
MvcJsonOptions jsonOptions = new MvcJsonOptions();
jsonOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonOptions.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
services.AddMvc(options => options = mvcOptions).AddJsonOptions(options => options = jsonOptions);
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromDays(365);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IOptions<JwtOptions> jwtOptions)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseDeveloperExceptionPage();
_jwtOptions = jwtOptions.Value;
app.UseAuthentication();
ConfigureAuth(app);
app.Map(new PathString("/api/images"), x => x.UseBlobFileViewHandler());
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute
(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"
);
routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
});
}
As you can see I tried to do some modification on the startup.cs class, but still can't figure out how it works. In the documentation everywhere is EF. What about us, who don't want to use the EF implementation.
public partial class Startup
{
private void ConfigureAuth(IApplicationBuilder app)
{
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_jwtOptions.SecretKey));
TokenProviderOptions tokenProviderOptions = new TokenProviderOptions
{
Path = "/api/token",
Audience = _jwtOptions.Audience,
Issuer = _jwtOptions.Issuer,
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
IdentityResolver = GetIdentity
};
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = _jwtOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = _jwtOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
LifetimeValidator = LifetimeValidator,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
app.UseSimpleTokenProvider(tokenProviderOptions, tokenValidationParameters);
}
private Task<ClaimsIdentity> GetIdentity(string email)
{
ServiceMessage<UserEntity> request = _userService.FindByEmailAsync(email).Result;
if (request != null && request.Success && request.ResultObject != null)
{
return Task.FromResult(CreateClaimsIdentity(request.ResultObject, "Token"));
}
// Credentials are invalid, or account doesn't exist
return Task.FromResult<ClaimsIdentity>(null);
}
private ClaimsIdentity CreateClaimsIdentity(UserEntity user, string authenticationType)
{
List<Claim> claimCollection = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Email, ClaimValueTypes.String),
new Claim(ClaimTypes.Role, user.Role, ClaimValueTypes.String),
new Claim(ClaimTypes.Name, user.Email.Split('#')[0], ClaimValueTypes.String),
new Claim(ClaimTypes.Expiration, DateTime.UtcNow.AddDays(365).Second.ToString(), ClaimValueTypes.DaytimeDuration)
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection, authenticationType);
return claimsIdentity;
}
private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters #params)
{
if (expires != null)
{
return expires > DateTime.UtcNow;
}
return false;
}
public static string FromHex()
{
string hex = Guid.NewGuid().ToString();
hex = hex.Replace("-", "");
byte[] raw = new byte[hex.Length / 2];
for (int i = 0; i < raw.Length; i++)
{
raw[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return Encoding.ASCII.GetString(raw);
}
}
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
{
if (context.HttpContext.Request.Path.StartsWithSegments("/api") && context.HttpContext.Response.StatusCode == 200)
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
//return base.RedirectToLogin(context);
return Task.FromResult((int)HttpStatusCode.Unauthorized);
}
}
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new Microsoft.AspNetCore.Authentication.AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
}
public class TokenProviderMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenProviderOptions _options;
private readonly ILogger _logger;
private readonly JsonSerializerSettings _serializerSettings;
private readonly TokenValidationParameters _tokenValidationParameters;
private readonly ISocialAuthentificationServices _socialAuthentificationServices;
public TokenProviderMiddleware(RequestDelegate next, IOptions<TokenProviderOptions> options, ILoggerFactory loggerFactory, ISocialAuthentificationServices socialAuthentificationServices, IOptions<TokenValidationParameters> tokenValidationParameters)
{
_socialAuthentificationServices = socialAuthentificationServices;
_next = next;
_logger = loggerFactory.CreateLogger<TokenProviderMiddleware>();
_options = options.Value;
_tokenValidationParameters = tokenValidationParameters.Value;
ThrowIfInvalidOptions(_options);
_serializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
}
public Task Invoke(HttpContext context)
{
//Add CORS to every response
context.Response.Headers.Add("Access-Control-Allow-Headers", new string[] { "Authorization", "Content-Type" });
context.Response.Headers.Add("Access-Control-Allow-Methods", new string[] { "OPTIONS", "POST", "GET", "DELETE", "PUT" });
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
if (context.Request.Method.Equals("OPTIONS", StringComparison.Ordinal))
{
context.Response.StatusCode = 204;
return _next(context);
}
// If the request path doesn't match, skip
if (!context.Request.Path.Equals(_options.Path, 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 = (int)HttpStatusCode.BadRequest;
return context.Response.WriteAsync("Bad request.");
}
_logger.LogInformation("Handling request: " + context.Request.Path);
return GetToken(context);
}
private async Task GetToken(HttpContext context)
{
TokenData headers = GetHeaderContext(context);
if (headers == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid encrypted token.");
return;
}
if (string.IsNullOrEmpty(headers.Provider))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Provider not definied.");
return;
}
else if (string.IsNullOrEmpty(headers.Token))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
else
{
var providers = (Mapurija.Models.Enum.Providers[])Enum.GetValues(typeof(Mapurija.Models.Enum.Providers));
if (!headers.Provider.Contains(headers.Provider))
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
}
ServiceMessage<UserEntity> validation = null;
int enumProvider = 0;
int.TryParse(headers.Provider, out enumProvider);
try
{
switch (enumProvider)
{
case (int)Mapurija.Models.Enum.Providers.Mapporia:
validation = await _socialAuthentificationServices.VerifyMapurijaTokenAsync(headers.Token);
break;
case (int)Mapurija.Models.Enum.Providers.Facebook:
validation = await _socialAuthentificationServices.VerifyFacebookTokenAsync(headers.Token);
break;
case (int)Mapurija.Models.Enum.Providers.Google:
validation = await _socialAuthentificationServices.VerifyFacebookTokenAsync(headers.Token);
break;
default:
validation = null;
break;
}
}
catch
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid request token!");
return;
}
if (validation == null || !validation.Success || validation.ResultObject == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync(validation.ErrorMessage);
return;
}
ClaimsIdentity identity = await _options.IdentityResolver(validation.ResultObject.Email);
if (identity == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token.");
return;
}
DateTime now = DateTime.UtcNow;
// Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
Claim[] claims = new Claim[]
{
new Claim(ClaimTypes.Name,validation.ResultObject.Email,ClaimValueTypes.String),
new Claim(JwtRegisteredClaimNames.Sub,validation.ResultObject.Email,ClaimValueTypes.String),
new Claim(JwtRegisteredClaimNames.Typ, validation.ResultObject.Role),
new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(now).ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Iss, _options.Issuer),
new Claim(JwtRegisteredClaimNames.Aud, _options.Audience)
};
// Create the JWT and write it to a string
JwtSecurityToken jwt = new JwtSecurityToken
(
issuer: _options.Issuer,
audience: _options.Audience,
claims: claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials
);
SecurityToken token;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
string encodedJwt = handler.WriteToken(jwt);
ClaimsPrincipal principal = handler.ValidateToken(encodedJwt, _tokenValidationParameters, out token);
if (token == null)
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await context.Response.WriteAsync("Invalid token generated!");
return;
}
var response = new
{
access_token = encodedJwt,
expires_in = (int)_options.Expiration.TotalSeconds
};
// Serialize and return the response
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));
}
private static void ThrowIfInvalidOptions(TokenProviderOptions options)
{
if (string.IsNullOrEmpty(options.Path))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Path));
}
if (string.IsNullOrEmpty(options.Issuer))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Issuer));
}
if (string.IsNullOrEmpty(options.Audience))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Audience));
}
if (options.Expiration == TimeSpan.Zero)
{
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(TokenProviderOptions.Expiration));
}
if (options.IdentityResolver == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.IdentityResolver));
}
if (options.SigningCredentials == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.SigningCredentials));
}
if (options.NonceGenerator == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.NonceGenerator));
}
}
private TokenData GetHeaderContext(HttpContext context)
{
string token = new StreamReader(context.Request.Body).ReadToEnd();
if (string.IsNullOrEmpty(token))
{
return null;
}
var encrypted = Convert.FromBase64String(token);
var decriptedFromJavascript = Mapurija.Services.Common.TokenDecrypter.DecryptStringFromBytes(encrypted, Mapurija.Services.Common.TokenDecrypter.KeyBytes, Mapurija.Services.Common.TokenDecrypter.Vi);
TokenData result = JsonConvert.DeserializeObject< TokenData>(decriptedFromJavascript);
return result;
}
/// <summary>
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// </summary>
/// <param name="date">The date to convert.</param>
/// <returns>Seconds since Unix epoch.</returns>
public static long ToUnixEpochDate(DateTime date) => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
}
public class TokenProviderOptions
{
/// <summary>
/// The relative request path to listen on.
/// </summary>
/// <remarks>The default path is <c>/token</c>.</remarks>
public string Path { get; set; } = "api/token";
/// <summary>
/// The Issuer (iss) claim for generated tokens.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// The Audience (aud) claim for the generated tokens.
/// </summary>
public string Audience { get; set; }
/// <summary>
/// The expiration time for the generated tokens.
/// </summary>
/// <remarks>The default is five minutes (300 seconds).</remarks>
public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(365);
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
/// <summary>
/// Resolves a user identity given a username and password.
/// </summary>
public Func<string, Task<ClaimsIdentity>> IdentityResolver { get; set; }
/// <summary>
/// Generates a random value (nonce) for each generated token.
/// </summary>
/// <remarks>The default nonce is a random GUID.</remarks>
public Func<Task<string>> NonceGenerator { get; set; } = new Func<Task<string>>(() => Task.FromResult(Guid.NewGuid().ToString()));

Resources