JWT token generated using jsonwebtoken library gives invalid signature in jwt.io - node.js

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.

Related

Generate a JWT in Node using inputs like jwt IO

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.

How to get access token from Azure Active Directory with certificate when service is behind proxy

I need to create service that calls graph api to access company data. In order to authenticate I need JWT token from Azure Active Directory. The authentication will be using application mode with signing certificate. I tried to use MSAL node ConfidentialClientApplication but the service needs to use http proxy to connect to internet. To my knowledge MSAL node does not support this and calls result in library being unable to resolve the address of "https://login.microsoftonline.com". How can I make MSAL node use the proxy or get JWT token without use od MSAL?
In order to get JWT token from azure active directory without MSAL node, one have to generate proper JWT token on its own and then sign it with certificate private key. The header of the token consists of following fields:
{
typ: "JWT",
alg: "RS256",
kid: "156E...",
x5t: "iTYVn..."
}
"kid" is the thumbprint of the certificate used to sign the request - here is a good example how to obtain it for pfx file with powershell https://stackoverflow.com/a/32980899/3588432
"x5t" is base64 encoded and sanitized certificate thumbprint.
Sanitization of base64 encoded string means:
trimming "=" signs at the end
replace "/" with "_"
replace "+" with "-"
Exemplary C# code for the sanitization:
var sanitized = s.Split('=')[0].Replace('+', '-').Replace('/', '_');
and JS code:
var sanitized = s.split('=')[0].replace('+', '-').replace('/', '_');
The payload of the token consists of the following fields:
{
aud: "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
iss: "{clientId}",
nbf: 1617952610,
exp: 1617953210,
sub: "{clientId}",
jti: "e13efcf..."
}
{tenantId} and {clientId} are Azure AD data of application we are authenticating to
"nbf" is the time when the token will began to be valid, normally it is time the token got generated. It has unix epoch format https://en.wikipedia.org/wiki/Unix_time and is an integer.
"exp" - the time the token expires in unix epoch format.
"jti" - a unique token identifier. It may be random generated guid. Should be different for every request.
An example how to get "nbf" value in JavaScript:
var nbf = Math.floor(new Date().getTime() / 1000);
When ready header and payload should be serialized (with sanitization) on concatenated with ".":
var token = JSON.stringify(header) + "." + JSON.stringify(payload);
Then we need to sign it with certificate private key, encode it with base 64 (with sanitization) and prepare a clientAssertion value:
var clientAssertion = token + "." + signedToken;
As a last step can send request to get JWT token:
const body = new URLSearchParams();
const token = await fetch("https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", {
agent: new HttpsProxyAgent("http://..."),
body: new URLSearchParams({
"client_assertion": clientAssertion,
"client_id": "{clientId}",
"scope": "https://graph.microsoft.com/.default"
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
"grant_type": "client_credentials"
}),
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded"
}
})
.then(response => response.json().access_token);

Using Google Cloud Key Management Service to sign JSON Web Tokens

Edit: I found the answer. Scroll to the bottom of this question.
I am working on a NodeJS authentication server and I would like to sign JSON Web Tokens (JWT) using google signatures.
I am using Google Cloud Key Management Service (KMS) and I created a key ring and an asymmetric signing key.
This is my code to get the signature:
signatureObject = await client.asymmetricSign({ name, digest })
signature = signatureObject["0"].signature
My Google signature object looks like this:
My question: How do I sign a JWT using the Google signature?
Or in other words, how do I concatenate the Google signature to the (header.payload) of the JWT?
The JWT should look something like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. (GoogleSignature)
The Code I am using:
signing:
async function sign(message, name) {
hashedMessage = crypto.createHash('sha256').update(message).digest('base64');
digest = { 'sha256': hashedMessage }
signatureObject = await client.asymmetricSign({ name, digest }).catch((err) => console.log(err))
signature = signatureObject["0"].signature
signJWT(signature)
}
Creating the JWT:
function signJWT(signature) {
header = {
alg: "RS256",
typ: "JWT"
}
payload = {
sub: "1234567890",
name: "John Doe",
iat: 1516239022
}
JWT = base64url(JSON.stringify(header)) + "." +
base64url(JSON.stringify(payload)) + "." +
???signature??? ; // what goes here?
}
Verifying:
async function validateSignature(message, signature) {
// Get public key
publicKeyObject = await client.getPublicKey({ name }).catch((err) => console.log(err))
publicKey = publicKeyObject["0"].pem
//Verify signature
var verifier = crypto.createVerify('sha256');
verifier.update(message)
var ver = verifier.verify(publicKey, signature, 'base64')
// Returns either true for a valid signature, or false for not valid.
return ver
}
The Answer:
I can use the toString() method like so:
signatureString = signature.toString('base64');
AND then I can get the original signature octet stream by using
var buffer = Buffer.from(theString, 'base64');
You did not post your code in your question, so I do not know how you are building the JWT for signing.
[EDIT 1/18/2019 after code added to question]
Your code is doing the signature backwards. You are creating a signature and trying to attach it to the JWT Headers + Payload. You want to instead take the JWT Headers + Payload and sign that data and then attach the signature to the JWT to create a Signed-JWT.
Psuedo code using your source code:
body_b64 = base64url(JSON.stringify(header)) + "." + base64url(JSON.stringify(payload))
signature = sign(body_b64, name);
jwt = body_b64 + '.' + base64url(signature)
Note: I am not sure what data format the signature is returned by signatureObject["0"].signature. You may have to convert this before converting to base64.
[END EDIT]
Example data:
JWT Header:
{
alg: RS256
kid: 0123456789abcdef62afcbbf01234567890abcdef
typ: JWT
}
JWT Payload:
{
"azp": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
"aud": "123456789012-gooddogsgotoheaven.apps.googleusercontent.com",
"sub": "123456789012345678901",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"exp": "1547806224",
"expires_in": "3596",
"email": "someone#example.com.com",
"email_verified": "true",
"access_type": "offline"
}
Algorithm:
SHA256withRSA
To create a Signed JWT (JWS):
Step 1:
Take the JWT Header and convert to Base-64. Let's call this hdr_b64.
Step 2:
Take the JWT Payload and convert to Base-64. Let's call this payload_b64.
Step 3:
Concatenate the encoded header and payload with a dot . in between: hdr_b64 + '.' + payload_b64`. Let's call this body_b64.
Step 4:
Normally a JWS is signed with SHA256withRSA often called "RS256" using the Private Key:
signature = sign(body_b64, RS256, private_key)
Now convert the signature to Base-64. Let call this signature_b64.
To create the final JWS:
jws = body_b64 + '.' + signature_b64.
Recommendations:
Do you want to use KMS to create Signed JWTs? I would not recommend this. There is a cost accessing keys stored in KMS. Signed-JWTs are signed with the private key and verified with the public key. How are you going to publish the public key? What performance level do you need in accessing the private and public keys (how often will you be signing and verifying)?
When you create a service account in Google Cloud Platform, a keypair is created for you. This keypair has an ID with the public key available on the Internet and the private key is present in the Service Account Json credentials file. I would use a Service Account to create Signed-JWTs instead of a keypair in KMS.
Example code in Python to create and sign:
def create_signed_jwt(pkey, pkey_id, email, scope):
'''
Create a Signed JWT from a service account Json credentials file
This Signed JWT will later be exchanged for an Access Token
'''
import jwt
# Google Endpoint for creating OAuth 2.0 Access Tokens from Signed-JWT
auth_url = "https://www.googleapis.com/oauth2/v4/token"
issued = int(time.time())
expires = issued + expires_in # expires_in is in seconds
# Note: this token expires and cannot be refreshed. The token must be recreated
# JWT Headers
headers = {
"kid": pkey_id, # This is the service account private key ID
"alg": "RS256",
"typ": "JWT" # Google uses SHA256withRSA
}
# JWT Payload
payload = {
"iss": email, # Issuer claim
"sub": email, # Issuer claim
"aud": auth_url, # Audience claim
"iat": issued, # Issued At claim
"exp": expires, # Expire time
"scope": scope # Permissions
}
# Encode the headers and payload and sign creating a Signed JWT (JWS)
sig = jwt.encode(payload, pkey, algorithm="RS256", headers=headers)
return sig

nodejs jsonwebtoken verify error with some tokens

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.

Decrypt or Validate Signature on Azure Mobile Services Token on the server side

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.

Resources