Is it possible to validate the JWT token in a reactjs frontend application or does it require server side node/other framework to validate the signature?
From what I understand the JWT token has a signature portion that has to be validated against a secret key. So for this to happen security you require this part of the validation to be perform on the server side/backend.
Is this correct?
You can decode the JWT's payload on the frontend without the key, but you'll need the key to validate the token (meaning verify it was generated using the given key).
Exposing the key to the frontend makes JWT useless because anyone can generate tokens with any payload value, signed with your key. Therefore, any validation of the token should be done on the server side with the protected key.
Example frontend decode. Credit to #Peheje:
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
Currently I am working in dev env on my local machine where I am storing passwords in plain text using MongoDB. I am using express-jwt along with jsonwebtoken for passing user data and authentication. I've researched bcryptjs and bcrypt and I would like to implement whichever is best for React, and Express, for passing hashed passwords to the database from the client. I have found resources for server side, but nothing for client side.
My question is then, what is the methodology for properly saving encrypted passwords on my server when they are passed from a client? How do I encrypt passwords client side then authenticate server side?
I have read some articles saying there is no need to encrypt client side due to ssl, but others say there is an absolute need to encrypt client side. What is the correct way, and how can I implement it on my React application?
Using Bcryptjs, Express and MongoDB:
There is no need to encrpyt client side, you can pass the password as plain text to the server using a post request (through a form usually).
Assuming you have a 'user' schema which looks similar to this:
const userSchema = new mongoose.Schema({
email:{type:String,required:true,unique:true},
password:{type:String,required:true}
},{collection:'users'}
const User= mongoose.model("User",userSchema);
)
On register/sign up in the server, where you handle the request you would hash the user's password like so:
app.post('/signup',async (req,res)=>{
// geting our data from frontend
const {email,password:plainTextPassword}=req.body;
// encrypting our password to store in database
const password = await bcrypt.hash(plainTextPassword,salt);
try {
// storing our user data into database
const response = await User.create({
email,
password
})
return res.redirect('/');
} catch (error) {
console.log(JSON.stringify(error));
if(error.code === 11000){
return res.send({status:'error',error:'email already exists'})
}
throw error
}
})
4.Upon login request (which will also be a post through a form o the client), you will compare the passwords using bcrpyt.compare() function, and if successful, assign a JWT to the user like so, this method assumes the token will be stored in the Cookies.
const verifyUserLogin = async (email,password)=>{
try {
const user = await User.findOne({email}).lean()
if(!user){
return {status:'error',error:'user not found'}
}
if(await bcrypt.compare(password,user.password)){
// creating a JWT token
token = jwt.sign({id:user._id,username:user.email,type:'user'},JWT_SECRET,{ expiresIn: '2h'})
return {status:'ok',data:token}
}
return {status:'error',error:'invalid password'}
} catch (error) {
console.log(error);
return {status:'error',error:'timed out'}
}
}
// login
app.post('/login',async(req,res)=>{
const {email,password}=req.body;
// we made a function to verify our user login
const response = await verifyUserLogin(email,password);
if(response.status==='ok'){
// storing our JWT web token as a cookie in our browser
res.cookie('token',token,{ maxAge: 2 * 60 * 60 * 1000, httpOnly: true }); // maxAge: 2 hours
res.redirect('/');
}else{
res.json(response);
}
})
I didn't address the front end, because it only includes basic POST request forms in react, there is no need for any special methods or processing on the client-side.
hope it helps.
edit, hashing client-side:
There is a debate about this, and in some protocols, it's even required to hash passwords on the client-side, in short, because SSL already encrypts everything that moves from client to server hashing on the client-side is pretty pointless and is not widely accepted today, even # big companies. Bottom line, the added security is neglectable, and is not worth the trouble and exposing hashing logic to the client side
You don't decrypt passwords. You ask the user for the password, then you hash it and compare it to the stored hash one you saved. If they're the same, then (assuming you have a secure hashing algorithm) the unencrypted versions must be the same also.
My identityserver4 is deployed into AKS with replicas to 1. But when I set the replica count to more than 1, I started seeing problems.
Sometimes authentication validation works sometimes it doesn't. As per the documentation from IdentityServer4 portal, if we use more than one instance of the identityserver is running then we have to make sure that the same signing credentials should be used across all the instances. I have used Azure keyvault Keys to for signing credentials following this article.
But I'm still facing the problem. I have also made sure that the discovery url is same even when there are multiple instances runnings as per this link. Please advise.
sample Startup code for fetching the Keys from Azure keyVault -
var keyClient = new KeyClient(
new Uri(""), // e.g. https://scottbrady91-test.vault.azure.net/
new ClientSecretCredential(
tenantId: "",
clientId: "",
clientSecret: ""));
Response<KeyVaultKey> response = keyClient.GetKey(""); // e.g. IdentityServerSigningKeyEcc
AsymmetricSecurityKey key;
string algorithm;
if (response.Value.KeyType == KeyType.Ec)
{
ECDsa ecDsa = response.Value.Key.ToECDsa();
key = new ECDsaSecurityKey(ecDsa) {KeyId = response.Value.Properties.Version};
// parse from curve
if (response.Value.Key.CurveName == KeyCurveName.P256) algorithm = "ES256";
else if (response.Value.Key.CurveName == KeyCurveName.P384) algorithm = "ES384";
else if (response.Value.Key.CurveName == KeyCurveName.P521) algorithm = "ES521";
else throw new NotSupportedException();
}
else if (response.Value.KeyType == KeyType.Rsa)
{
RSA rsa = response.Value.Key.ToRSA();
key = new RsaSecurityKey(rsa) {KeyId = response.Value.Properties.Version};
// you define
algorithm = "PS256";
}
else
{
throw new NotSupportedException();
}
services.AddIdentityServer()
.AddTestUsers(TestUsers.Users)
.AddInMemoryIdentityResources(Config.Ids)
.AddInMemoryApiResources(Config.Apis)
.AddInMemoryClients(Config.Clients)
.AddSigningCredential(key, algorithm);
Another thing that might give you issues is the Data Protection API, it is in charge of protection the session cookies involved. If you want the session cookies to be valid across the instances, then they need to share the same encryption keys (key ring).
See Configure ASP.NET Core Data Protection
I also did blog about this here:
Storing the ASP.NET Core Data Protection Key Ring in Azure Key Vault
I want to prevent my secret data (e.g. 'password') while sending them in my requests.
I'm using React on Frontend and MongoDB on Backend side.
Actually, I'm registering a user to database with his salted and hashed password like this:
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) {
next()
}
const salt = await bcrypt.genSalt(10);
console.log('this.password: ', this.password);
// password coming form Frontend is not still protected here, like '1234'
this.password = await bcrypt.hash(this.password, salt);
// password is protected like '$2a$10$gxNPkFvqRIFZPyMsB.Dmf.G52yQntT3LxJQHuteCaSZCpUZ0RPkdm'
})
But I want to protect the sensitive data also on the way (for example from 'man in the middle attacks').
So, how should I implement the sending of user password as protected, or what is the best experienced way to do it?
Thanks.
Use asymmetric encryption.
Generate a public-private key pair, encrypt the password with the public key on the frontend, send the ciphertext to the backend, decrypt with the private key on the backend.
CCAvenue does not provide integration kit for Salesforce Apex language. They provide Asp.net, Java, NodeJS, iOS, Android, Windows.
How do we encrypt data and make request for CCAvenue payment gateway?
After lots of struggle we managed to get encrypt and dcrypt data in salesforce apex.
Here is Encryption:
/*
This PLAIN_TEXT is your data collected from your apex form. Few values are required and lots of values are optional. Please read document provided by ccavenue.
*/
String PLAIN_TEXT = 'tid=XXXX&merchant_id=XXXX&order_id=XXXX&amount=XX¤cy=INR&redirect_url=XXXX&cancel_url=XXXX&language=EN&billing_name=XXXX&billing_address=XXXX&billing_city=XXXX&billing_state=XX&billing_zip=XXXX&billing_country=XXXX&billing_tel=XXXX&billing_email=XXXX&delivery_name=XXXX&delivery_address=XXXX&delivery_city=XXXX&delivery_state=XXXX&delivery_zip=XXXX&delivery_country=XXXX&delivery_tel=XXXX&merchant_param1=XXXX&merchant_param2=XXXX&merchant_param3=XXXX&merchant_param4=XXXX&merchant_param5=XXXX&promo_code=&customer_identifier=&';
//WORKING_KEY is key provided by CCAvenue when you register as Merchant.
Blob cryptoKey = Blob.valueOf('WORKING_KEY');
Blob hash = Crypto.generateDigest('MD5', cryptoKey );
Blob data = Blob.valueOf(PLAIN_TEXT);
Blob encryptedData = Crypto.encryptWithManagedIV('AES128', hash , data);
String encRequest = EncodingUtil.convertToHex(encryptedData );
/*Pass this encRequest with access_code to the https://secure.ccavenue.com/transaction/transaction.do?command=initiateTransaction using visualforce FORM
*/
Here is Decryption:
Blob cryptoKey = Blob.valueOf('WORKING_KEY');
Blob hash = Crypto.generateDigest('MD5', cryptoKey);
Blob data = EncodingUtil.convertFromHex('ENC_RESPONSE'); //Received from ccAvenue response
Blob decryptedText = Crypto.decryptWithManagedIV('AES128', hash, data);
String PLAIN_TEXT = decryptedText.toString();