Verify Token generated in C# with System.IdentityModel.Tokens.Jwt - javascript

I have created a token like this in my web api with C#.
private const string Secret = "someSecretKey";
public static string GenerateToken(AuthModel user, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Role, ((Roles)user.RoleId).ToString()),
new Claim("guid",user.Guid)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
and when im usin that API for reactjs app i get the token but cant verify it with same secret key.
Im getting error INVALID SIGNATURE.
Im using jsonwebtoken npm package,
import jwt from 'jsonwebtoken';
jwt.verify(token, keys.jwtSecret, async (err) => {
if (err) {
//console.log('Token expired at: ', err.expiredAt)
console.log("error", err)
}
else {
dispatch(login(token));
}
});
i never hit that dispatch(login(token)). I'm using this to check if token saved in localStorage is still valid to keep user signed in.
Any help is appreciated.

I've found solution. Couldn't just push secretKey in jwt.verify(token,secretKey); That doesn't work because some base64 encoding/decoding algorithms. What i had to do is first to make a Buffer from my secret like:
const secret = new Buffer("myTokeSecretString", "base64");
and then pass that secret to verify method and it works.

Related

How do i get RSACryptoServiceProvider to verify a message using public key and signature

I generated a private and public key in javascript like this.
import crypto from "crypto";
/*export const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
});*/
const pair = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
export const privateKey = pair.privateKey.export({
type: "pkcs1",
format: "pem",
});
export const publicKey = pair.publicKey.export({
type: "pkcs1",
format: "pem",
});
Then i use the private key to create a signature for a jsonfile like this, and the public key to verify it before i return the signature.
//Lav signatur
const signate = crypto.createSign("SHA384");
signate.update(Buffer.from(licenseRelationship, "utf-8"));
const signature = signate.sign(privateKey, "hex");
const verifier = crypto.createVerify("SHA384");
// verificer signature, besked
verifier.update(Buffer.from(licenseRelationship, "utf-8"));
const verificationResult = verifier.verify(publicKey, signature, "hex");
This works perfectly, and then i return the json and the signature as a http response.
I recieve it in c# code and store the two components so im can use them later on request.
Upon request i fetch the two components and want to use the signature to check if the json has been tampered with.
I also has the public key in this code.
I do it like this.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature,
HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
if (result)
{
log.write("Message verified ", null);
} else
{
log.write("Message not Verified ", null);
}
All debug code and exception handling removed.
I'm a crypto virgin, and am trying to understand this. But i must have misunderstood something serious.
I have the public key as a string (not base64 encoded)
Ive checked the json, and it is the exact same bytes when signed in Javascript as when being verified in c#
The public key is not used in this process. That has to be wrong i think ?
How do i get the public key into the RWACryptoServiceProvider ?
Im sure im using RWACryptoServiceProvider wrong.
EDIT....:
Ive tried this instead, but still to no avail.
string licenseRelationshipJson = licenseRelationshipDAO.getLicenseRelationshipWithoutSignatureAsJson(licenseRelationship);
byte[] signature = Encoding.UTF8.GetBytes(licenseRelationship.signature);
byte[] licenseRelationshipJsonAsArray = Encoding.UTF8.GetBytes(licenseRelationshipJson);
byte[] asBytes = Encoding.ASCII.GetBytes(DataStorage.Instance.PUBLIC_KEY);
char[] publicKeyAsArray = Encoding.ASCII.GetChars(asBytes);
ReadOnlySpan<char> publicKeyChars = publicKeyAsArray;
RSA rsa = RSA.Create();
try
{
rsa.ImportFromPem(publicKeyChars);
result = rsa.VerifyData(licenseRelationshipJsonAsArray, signature, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1);
} catch (CryptographicException cex)
{
log.write("Something went wrong with the crypto verification process", cex);
}
.
.
.
Thankyou for your time.

How to handle DirectLine connection errors

we run a bot using bot framework and connect to it from our website using direct line in JS. We get a token from a custom API endpoint and we store the token in sessionStorage. Then we connect to the bot using
directLine = await window.WebChat.createDirectLine({
token,
conversationId,
watermark: "0"
});
Everything is working fine, but when I leave the page open for too long the token in the sessionStorage expires. A page refresh or navigating to a different page causes a 403 error inside the createDirectLine method. Resulting in a chat bot that can't connect for as long as the sessionStorage holds that token. This behavior is not a surprise to me, but I don't know how to handle this.
What I want is to simply clear the sessionStorge, request a new token and start a new conversation when this happens. But I don't know how to do that. How do I get the 403 error from the createDirectLine method? Or is there a way to validate the token upfront?
I already tried putting a try/catch block around the createDirectLine method, but the 403 error did not show up in the catch.
Thanks in advance!
This solution is only to address the 403 error from occurring since the token expires (in 30 minutes I think). A better solution is to store the conversationId with the token and get a new token with that. Check official bot service documentation for that.
// to shorten code, we store in sessionStorage as separate items.
const expirationDuration = 1000 * 60 * 30; // 30 minutes
const currentTime = new Date().getTime();
const timeTokenStored = sessionStorage.getItem("timeTokenStored") || currentTime;
// if token is stored over 30 minutes ago, ignore it and get a new one. Otherwise, use it.
if ((currentTime - timeTokenStored) > expirationDuration) {
const res = await fetch('https://<yourTokenEndpoint>', { method: 'POST' });
const { token } = await res.json();}
const currentTime = new Date().getTime();
sessionStorage.setItem("timeTokenStored", currentTime);
sessionStorage.setItem('token', token);
else {
const token = sessionStorage.getItem("token")
}
While your're at it, you might as well store it in localStorage. This way, your bot will follow the user.
I have found the solution. We can check that a token is valid by refreshing it. If refreshing causes an error, the token is not valid anymore. If refreshing succeeds, the toke will be valid for another hour.
So we added (resusable) a function to our back end to refresh the token using https://directline.botframework.com/v3/directline/tokens/refresh.
And we changed the front end code to call our new refresh function.
Front end code:
// Gets a new token from the cloud.
async function requestToken() {
if (!sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token');
// If the request was succesfull, store the token and userId.
if (res.status == 200) {
const jsonResult = await res.json();
sessionStorage['webchatToken'] = jsonResult.token;
sessionStorage['webchatUserId'] = jsonResult.userId;
console.log(`Got token from cloud`);
// refresh the token every 15 minutes.
setTimeout(() => {
refreshToken();
}, 60000 * 15); // 15 minutes
}
// If the request was not succesfull, retry.
else {
console.log(`Tried to get token, but goterror ` + res.status + `. Retrying.`);
await requestToken();
}
}
// If there is already a token in storage, refresh the existing one instead of requesting a new one.
else {
console.log(`Got token from sessionStorage`);
await refreshToken();
}
}
// Refreshes an existing token so it doesn't expire.
async function refreshToken() {
// Refresh the token if it exists in storage.
if (sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token/refresh?token=' + sessionStorage['webchatToken'],
{
method: 'POST'
});
// If refresh was succesfull we are done.
if (res.status == 200) {
console.log(`Refreshed token`);
}
// If refresh was not succesfull, clear the token from storage and request a new one. The token is probably expired.
else {
console.log(`Tried to refresh token, but got error ` + res.status + `. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
// If there is no token in storage, request a new token.
else {
console.log(`Tried to refresh token, but token is not defined. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
Back end code:
[HttpGet]
[Route("api/token")]
public async Task<ObjectResult> GetToken()
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.DirectLineKey);
var userId = $"dl_{Guid.NewGuid()}";
request.Content = new StringContent(
JsonConvert.SerializeObject(new { User = new { Id = userId } }),
Encoding.UTF8,
"application/json");
var response = await client.SendAsync(request);
string token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
return Ok(new { token, userId, expiresIn });
}
[HttpPost]
[Route("api/token/refresh/")]
public async Task<ObjectResult> RefreshToken(string token)
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/refresh");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
if (string.IsNullOrEmpty(token))
return Problem("Token incorrect");
return Ok(new { token, expiresIn });
}
I hope posting this may be useful to somebody.

How to connect Wix Answers to my SSO server

I made a makeshift 'sso server' which I want to connect to Wix Answers. Wix is a CMS. Wix Answers is a help center where you can customer FAQs, have customers receive support videos, and the customer can enter tickets. Wix answers are what Wix uses for Wix's help pages and they make the same app available to users for the same purpose.
My 'sso server' is an AWS API Gateway pointing to a Lambda function. Pretty straight forward. You call the public endpoint and it runs this lambda:
var express = require('express');
var app = express();
var crypto = require('crypto'); //npm install crypto --save
var base64url = require('base64url'); //npm install base64url --save
var bodyParser = require('body-parser');
var KEY_ID = '1234567'; //note it's a uuid
var SECRET = '1234567xxxxxxxxxxxxxxxxxxxxxxxxxxx';
exports.handler = async (event) => {
//this assumes there is a login or some UI that will receive the needed redirect url
app.get('/login-form', function (request, response) {
var url = require('url');
var urlParts = url.parse(request.url, true);
var query = urlParts.query;
var answersRedirectUrl = query.redirectUrl;
//of course, in a real system the data will come from your own user system
var dummyUserData = {
id: 'your-user-id',
email: 'user#email.com',
firstName: 'Bob2',
lastName: 'Bobson',
profileImage: 'https://i.ytimg.com/vi/-90CAdWk27I/maxresdefault.jpg',
timestamp: Date.now()
};
var token = encryptUserData(JSON.stringify(dummyUserData), SECRET);
response.redirect(answersRedirectUrl + '&token=' + token + '&key=' + KEY_ID);
});
};
function encryptUserData(data, key) {
var iv = new Buffer('');
var bytes = new Buffer(key, 'utf-8');
var hashedKey = crypto.createHash('sha1').update(bytes).digest().slice(0, 16);
var cipher = crypto.createCipheriv('aes-128-ecb', hashedKey, iv);
var crypted = cipher.update(data, 'UTF-8', 'hex');
crypted += cipher.final('hex');
return base64url(new Buffer(crypted, 'hex'));
}
This code is a lambda modified version of the code Wix Answers sample js code from, here.
https://help.wixanswers.com/en/article/setting-up-single-sign-on-sso-for-your-users
There are dependencies, and I have loaded them all into a lambda, so its not a dependency issue.
Wix Answers is an easy setup, you give them a url for login and logout. you generate a key inside of Wix answers dashboard, and I have added that key to my lambda below (the ones below are masked obviously). I've added my endpoint to the field in wix answers.
I'm getting a null response and was able to get an object with object.message = "missing auth token"
Focusing on the JS and the lambda, is there anything that I am leaving out that would make this not work. Again not a lot of experience with express and these dependencies, or with SSO.
Thanks!!
Wix Has some good tools but lacks documentation sometimes. Here is how I solved this problem:
tutorial https://help.wixanswers.com/en/article/setting-up-single-sign-on-sso-for-your-users
their JS code from https://gist.github.com/GabiGrin/0c92ecbb071e02e2d91c8d689517acd7#file-answers-sso-example-js
What I did
//encrypt.js
var crypto = require('crypto'); //built in to node
var base64url = require('base64url'); //requires install 'npm install base64url'
var KEY_ID = 'abcedfghijklmnopqrstuvwxyz'; //from Wix Answers
var SECRET = 'fakefakefakefakefakefake'; //from Wix Answers
let user = {
id: '123456',
email: 'email#domain.com',
firstName: 'Homer',
lastName: 'Simpson',
profileImage: 'https://i.ytimg.com/vi/-90CAdWk27I/maxresdefault.jpg',
timestamp: Date.now()
};
function encryptUserData(data, key) {
var iv = new Buffer('');
var bytes = new Buffer(key, 'utf-8');
var hashedKey = crypto.createHash('sha1').update(bytes).digest().slice(0, 16);
var cipher = crypto.createCipheriv('aes-128-ecb', hashedKey, iv);
var crypted = cipher.update(data, 'UTF-8', 'hex');
crypted += cipher.final('hex');
return base64url(new Buffer(crypted, 'hex'));
}
var token = encryptUserData(JSON.stringify(user), SECRET);
console.log(`https://mysite.wixanswers.com/api/v1/accounts/callback/sso?redirectUrl=https%3A%2F%2Fmysite.wixanswers.com%2Fen&token=${token}&key=${KEY_ID}`);
Since my project is a concept, I have not integrated with a real identity server, as you can see i skip the authentication and go right to authorization (for my hard coded user: Homer Simpson). To mkae it work i'd need to add authentication and pass a dynamic user object to an export function in the module.
for the sake of the concept tho:
from the shell 'node encrypt.js'
returns a redirect URL in the console - when followed, successfully signs you into the Wix Answers platform with the proper user object.

Azure DataLake File Download From Javascript

I was trying to download the file from azure data lake storage. it's working on c# side using Rest API. but it's not working in a java script.
My Sample c# code is
//Get Access Token
public DataLakeAccessToken ServiceAuth(string tenatId, string clientid, string clientsecret)
{
var authtokenurl = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", tenatId);
using (var client = new HttpClient())
{
var model = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type","client_credentials"),
new KeyValuePair<string, string>("resource","https://management.core.windows.net/"),//Bearer
new KeyValuePair<string, string>("client_id",clientid),
new KeyValuePair<string, string>("client_secret",clientsecret),
};
var content = new FormUrlEncodedContent(model);
HttpResponseMessage response = client.PostAsync(authtokenurl, content).Result;
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var accessToken = JsonConvert.DeserializeObject<DataLakeAccessToken>(response.Content.ReadAsStringAsync().Result);
return accessToken;
}
else
{
return null;
}
}
}
File Download Code is
public void DownloadFile(string srcFilePath, ref string destFilePath)
{
int i = 0;
var folderpath = Path.GetDirectoryName(destFilePath);
var filename = Path.GetFileNameWithoutExtension(destFilePath);
var extenstion = Path.GetExtension(destFilePath);
Increment:
var isfileExist = File.Exists(destFilePath);
if (isfileExist)
{
i++;
destFilePath = folderpath+filename + "_" + i + "_" + extenstion;
goto Increment;
}
string DownloadUrl = "https://{0}.azuredatalakestore.net/webhdfs/v1/{1}?op=OPEN&read=true";
var fullurl = string.Format(DownloadUrl, _datalakeAccountName, srcFilePath);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _accesstoken.access_token);
using (var formData = new MultipartFormDataContent())
{
var response = client.GetAsync(fullurl).Result;
using (var fs = new FileStream(destFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
{
response.Content.CopyToAsync(fs).Wait();
}
}
}
}
first i was Generate the token using client credentials and the token based download file using path example https://mydatalaksestore.azuredatalaksestore.net/myfolder/myfile i pass myfolder/myfile in source path and destFilePath file name based download the file
in javascript i was get the accesstoken from my api server and send the request for mydatalakestore it's throw error for cross orgin for localhost:8085 like this
Any one know how to download the datalake store file using Javascript from Client Side using Access Token without cross orgin error
Thanks in Advance

JsonWebToken encode is undefined

I'm creating my first ever login system, using JsonWebTokens, and I've hit an obstacle trying to use the encode function.
My error message says:
if (this.ended && !this.hasRejectListeners()) throw reason;
^ TypeError: undefined is not a function
and I've managed to narrow the error down to this line of code:
var jwt = require('jsonwebtoken');
jwt.encode(payload, superSecret);
The function this is a part of looks like this:
var moment = require('moment');
var jwt = require('jsonwebtoken');
var superSecret = require('../config/token.js').secret;
function (user) {
var payload = {
sub: user._id,
iat: moment().unix(),
exp: moment().add(14, 'days').unix()
};
return = jwt.encode(payload, superSecret);
Needless to say, since this is the first time I'm authenticating anything, I don't know why this would cause an error. Please help.

Categories

Resources