I've got a java service that generates json web tokens signed with RS256.
Then a service in node verifies the tokens with the public key using the jsonwebtoken module.
The problem is that some tokens work and some do not. According to jwt.io every token is correct.
This an example code with a working one and a failing one
var sanitize = function(data){
return data.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/g, '')
}
var jwtGood = sanitize("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6InBlbGF5by11c2VybmFtZTMiLCJlbWFpbCI6InBlbGF5by5yYW1vbisxMkBjbG91ZGRpc3RyaWN0LmNvbSIsInJvbGVzIjpbIlJPTEVfVVNFUiJdLCJpZCI6InVhb2V1YW9lb2FldTMiLCJpYXQiOjE0NTYxNDkwMjR9.tmvgtpuyuUiql2aYeR38kGTeQUwyb7XZr6Df2iv09_nxDn4HltHZm7Fvbj07ZQ5Hh_DmvlqZHz7EVSV6mERdjkohxf8tt9-J6NW_ftnUurCfLIzCcqEJ4xlKOzIgGsGrRd4ZUhw2hs4ZNTIscUb37csvKV-jPdSdQ-TxzuWZen4QnEUGvyg0VhdlU90TGZmpzobfpbHMQ3C0qhGRDMjghgej8zjWHbRDFRIGtAHLDbYVMiQRdI_GODIco2uSVh0_9PATSeRhFosHf3P3R4ohyBMrn9rxmBW4bQyFEMXWtsl4_PrKsdsaTtKjVQ2YuL5GjKQJqkWp6vx2vIxRabHz7w==");
var jwtBad = sanitize("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6InBlbGF5by11c2VybmFtZSIsImVtYWlsIjpudWxsLCJyb2xlcyI6WyJST0xFX1VTRVIiXSwiaWQiOm51bGwsImlhdCI6MTQ1NjMwMTUwN30=.X_vogPRHoE-ws2DxB8Q3wlm5JCQdOvuedhUC-1BlGa9qPdg5nmAGLoLuuGmQZ9r2yUD45OqKQ8_PVd05b0gQBhlIIWtQsXMSWypN6o43noZqMG6aM-GeAK-edDg2C7zw0yGQDD1BNLKBeWc8lNPzJAqQV0il_lg6bytIeN2LMAgxj78RZro3snkXN4woe6afCefW78z3KiOIQ2qI3pcA6Kf4j9NErHwfe9BP2dnV3mXTOZ8SIds_C9JWb7nt9o6Z4oCpskmXxhRCpP4ptTS0krGKfzfhYMKj2e7uOwS1pV4MdpQBeLlhZaGn3pmG5kwl3ZzEeIANfE7N8a9LofmFsQ==");
var jsonwebtoken = require('jsonwebtoken');
var fs = require('fs');
var pubKey = fs.readFileSync('public.pem');
console.log(jwtGood);
jsonwebtoken.verify(jwtGood, pubKey,{ algorithms: ['RS256'] },function(err, decoded){
console.log(err,decoded);
});
console.log(jwtBad);
jsonwebtoken.verify(jwtBad, pubKey,{ algorithms: ['RS256'] },function(err, decoded){
console.log(err,decoded);
});
The main difference just by looking at them is the "=" character at the end of the encoded payload. Working one payload ends in "MjR9." and failing one in "wN30=."
Removed the "=" by hand (as i did with the ending ones) but, according to jwt.io, without it the token is not verified.
Tried some more sanitize functions but they didn't work.
My guess is that there is a base64 encoding issue here but I can't find it.
Related
I have a header, payload, and a public/private key. I can plug these all into JWT.io and it works as expected, but I'm struggling how to use these same variables with a node library like jsonwebtoken or other similar options. They seem to take a secret and sign a payload as far as I can see, which doesn't seem to line up with my inputs. I need to dynamically generate this token request so I must have the function in Node.
Thanks for any tips.
Have a look at the jsonwebtoken NPM package, which offers amongst other methods, a sign method:
var jwt = require('jsonwebtoken');
var privateKey = fs.readFileSync('private.key');
var payload = { foo: 'bar' };
var token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
As #jps has pointed out, you need the private key to sign and the public key to verify.
The header will be automatically generated and will include both properties (alg and typ) you have mentioned in your comment. You can add additional properties by passing them in the options.header parameter.
I'm struggling how to use these same variables with a node library
import * as jose from 'jose';
const privateKey = await jose.importPKCS8(privateKeyPEM); // private key just like on jwt.io
const jwt = await new jose.SignJWT(payload) // payload just like on jwt.io
.setProtectedHeader(header) // header just like on jwt.io
.sign(privateKey);
Of course there's more to be discovered if you need it.
I have a bot in NodeJS connected to Google Chat using HTTPs endpoints. I am using express to receive requests. I need to verify that all requests come from Google, and want to do this using the Bearer Token that Google Sends with requests.
My problem is that I am struggling to find a way to verify the tokens.
I have captured the token and tried a GET reuqes to https://oauth2.googleapis.com/tokeninfo?id_token=ey... (where ey... is the token start).
Which returns:
"error": "invalid_token",
"error_description": "Invalid Value"
}
I have tried what Google recommends:
var token = req.headers.authorization.split(/[ ]+/);
client.verifyIdToken({
idToken: token[1],
audience: JSON.parse(process.env.valid_client_ids)
}).then((ticket) => {
gchatHandler.handleGChat(req.body, res);
}).catch(console.error);
And get the following error:
Error: No pem found for envelope: {"alg":"RS256","kid":"d...1","typ":"JWT"}
Any idea where I should head from here?
Edit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com found this, investigating how to use it. The kid matches the one I get.
Worked it out, eventually.
You need to hit: https://www.googleapis.com/service_accounts/v1/metadata/x509/chat#system.gserviceaccount.com to get a JSON file containing the keys linked to their KIDs.
Then when a request arrives, use jsonwebtoken (NPM) to decode the token and extract the KID from the header.
Use the KID to find the matching public key in the response from the website above, then use the verify function to make sure the token matches the public key.
You also need to pass the audience and issuer options to verify, to validate that it is your particular service account hitting the bot.
The solution above maybe the correct for Google Chat, but in my experience Google services (e.g. Google Tasks) use OIDC tokens, which can be validated with verifyIdToken function.
Adding my solution here, since your question/answer was the closest thing I was able to find to my problem
So, In case if you need to sign a request from your own code
on client, send requests with OIDC token
import {URL} from 'url';
import {GoogleAuth} from 'google-auth-library';
// will use default auth or GOOGLE_APPLICATION_CREDENTIALS path to SA file
// you must validate email of this identity on the server!
const auth = new GoogleAuth({});
export const request = async ({url, ...options}) => {
const targetAudience = new URL(url as string).origin;
const client = await auth.getIdTokenClient(targetAudience);
return await client.request({...options, url});
};
await request({ url: 'https://my-domain.com/endpoint1', method: 'POST', data: {} })
on the server, validate OIDC (Id token)
const auth = new OAuth2Client();
const audience = 'https://my-domain.com';
// to validate
const token = req.headers.authorization.split(/[ ]+/)[1];
const ticket = await auth.verifyIdToken({idToken: token, audience });
if (ticket.getPayload().email !== SA_EMAIL) {
throw new Error('request was signed with different SA');
}
// all good
Read more about Google OpenID Connect Tokens
In short : the token generated using below code gives the correct headers and payload(when I paste the generated token in JWT.io).
It only works when I insert the secret and press the secret encoded checkbox in jwt.io. After that I get valid token.
But var token = jwt.sign(payload, privateKEY, signOptions); this step should do the same thing I guess.
My code.
var jwt = require('jsonwebtoken');
var payload = {
"userId" : 'YYYYYYYYYYYYYYYYYYYYYYY',
"iat" : new Date().getTime(),
};
var signOptions = {
algorithm: "HS512"
};
var privateKEY = 'XXXXXXXXXXXXXXXXXXXXXXXX';
var token = jwt.sign(payload, privateKEY, signOptions);
console.log("Token :" + token);
This gives me an invalid token but when i paste that token in jwt.io I get the correct Headers and Payload.
And if I insert my secret and press the checkbox I get the correct token.
What I am I doing wrong. Thanks
When you check the checkbox on jwt.io, it base64 decodes your secret. Since you don't base64 encode your secret in your code, you shouldn't check that box on jwt.io. Both tokens are correct, but for different secrets. If you want the same token that you got from jwt.io with the box checked, you can use this:
var decodedSecret = Buffer.from(privateKEY, 'base64');
Then use that to sign your token instead of privateKEY. However, this doesn't really make sense, as your key isn't base64 encoded to begin with.
using jsonwebtoken
https://www.npmjs.com/package/jsonwebtoken
var jwt = require('jsonwebtoken');
var token = httpResponse.headers["x-authorization-bearer"].trim();
var decoded = jwt.decode(token);
console.log(token); // eyJ0eXAiOiJKV1QiLCJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.WTMwOGJudHBDTVM3Rm52clBPOGFPUQ.UbXYtb5KppbGYn3AkyOkCg.ljnC5I8q3qThn-NHY6qBqkFhSS9hNiR_pviIFB1zNVmp5Z2wOx0MON2sWRsDF__uSJ-PdI7QaM6djdflbTvKyPWbtKV6g_VDOU-lF6XKMI96BMK41mmBiJSNyDNxE5hqB4X_qWeCYMif8tf583bcKvkrxyuUTsRwvR2Xdo6yl9dyapYGhvKar2TtogOR9-jlFADfPL07ih0YjPYTo2gAWGzrVR6tNuyoRJolYd0ixon5nZ1aP5TdcbPrNcWmGfmuIfWN12BdiEtfrVYDNV7xwmNWfuxke0Uev5VAlIATg_U.1X6R6y9IK3n8NAexswUQKQ
console.log(decoded); // null
The JWT you've given has 4 . characters, but according to jwt.io:
JSON Web Tokens consist of three parts separated by dots (.), which are:
Header
Payload
Signature
Therefore, a JWT typically looks like the following.
xxxxx.yyyyy.zzzzz
The decode function in jsonwebtoken first calls out to a decode function in jws, which validates against the following RegEx:
/^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?$/
Given that your token contains 4 . characters, this validation fails and returns null, which propagates up to your call site.
If you are trying to instantiate the JSON Web token you wont get the unique token value.In fact you may end up with {} empty inject.
We can make use of the jsonwebtoken in below fashion.
eg. const token = jwt.sign(payload,SECRET_KEY,{algorithm,expireIn})
After following the guide for creating a custom identity provider for azure mobile services I can easily generate the appropriate tokens. The code is pretty simple and looks like this:
var userAuth = {
user: { userId : userId },
token: zumoJwt(expiry, aud, userId, masterKey)
}
response.send(200, userAuth);
The definitions for the parameters and code for zumoJwt are located at the link. Azure automatically decodes the token and populates the user on the request object which is what I'd like to simulate.
Basically I'd like to to decrypt the token on the serverside via Node (not .net).
What I ended up doing to validate the token is the following (boiled down). This seems to be about what the azure mobile services is doing on routes that require authorization.
var jws = require('jsw'); // https://github.com/brianloveswords/node-jws
function userAuth() {
var token = ''; // get token as header or whatever
var key = crypto.createHash('sha256').update(global.masterKey + "JWTSig").digest('binary');
if (!jws.verify(token,key)) {
// invalid token logic
} else {
var decode = jws.decode(token)
req.user = {
userId: decode.payload.uid.split(';')[0].split('::')[0]
};
next();
}
}
app.use(authChecker);
The tokens aren't really encrypted - they're just signed. The tokens have the JWT format (line breaks added for clarity):
<header>, base64-encoded
"."
<envelope>, base64-encoded
"."
<signature>, base64-encoded
If you want to decode (not decrypt) the token in node, you can split the value in the . character, take the first two members, base64-decode them (var buffer = new Buffer(part, 'base64')), and convert the buffer to string (buffer.toString('utf-8')).
If you want to validate the token, just follow the same steps you need to re-sign the first two parts of the token (header + '.' + envelope) with the master key, in the same way that the token was created, and compare it with the signature you received on the original token.