Is there a native method in nestjs to decode JWT? - jwt

In nestjs I create JWT (tokens) by creating a payload object and signing it. Something like this:
const jwtPayload: JwtPayload =
{
iss: issuer,
sub: info,
aud: audience,
// exp: - populated by fn: this.jwtService.sign(payload),
// iat: - populated by fn: this.jwtService.sign(payload),
jti: 'XXXX1234'
}
const signedJwtAccessToken: string = this.jwtService.sign(jwtPayload);
Nest encodes the jwtPayload into a string.
For cleanup work I would like to know when exactly the JWT expires. This is automatically encoded into the 'signedJwtAccessToken' - property .exp - by the .sign() function.
To access it right after the signing, it needs to be decoded.
What would be the simplest way to decode the signedJwtAccessToken in the same method right after it has been signed ???
Note:
When the JWT comes back from client, nestjs decodes it when accessing the fn: validate(), but I want to decode right after signing it - before sending the response to client, something like:
// signing - encoding
const signedJwtAccessToken: string = this.jwtService.sign(jwtPayload);
// decoding
const decodedJwtAccessToken: string = decodeJwt(signedJwtAccessToken);
// parsing back to an object
const updatedJwtPayload: JwtPayload = JSON.parse(decodedJwtAccessToken);
// reading property of .exp
const expires = updatedJwtPayload.exp;

Just decode it as a normal jwt token. If you use nestjs-jwt package, just call decode function:
const decodedJwtAccessToken: JwtPayload = this.jwtService.decode(signedJwtAccessToken);
const expires = decodedJwtAccessToken.exp;
Or just decode the token as a base64 string
const base64Payload = signedJwtAccessToken.split('.')[1];
const payloadBuffer = Buffer.from(base64Payload, 'base64');
const updatedJwtPayload: JwtPayload = JSON.parse(payloadBuffer.toString()) as JwtPayload;
const expires = updatedJwtPayload.exp;

Related

Wrap JWS in another JWS

I have a JWT which is created by a source server. Before the JWT goes to the destination server it passes through another intermediate service which verifies, adds some extra data and signs. I need the destination to verify it came from both services and obtain the extra_data
I was thinking the final JWT will look like the following
const token = JWT({
sub: 'ey....,
extra_data: 123,
})
and the code I will use to verify and get data would look like:
const { sub, extra_data } = jwt.verify(token, publicKeyIntermediate);
const { data } = jwt.verify(sub, publicKeySource);
const finalPayload = {
extra_data,
...data,
}
Is this the correct way to deal with this situation?

Unable to verify message signed by sol-wallet-adapter

Having created a signed message I'm unsure how to use the resulting signature to verify the message using the publicKey.
My use case is, I'm wanting to use a Solana Wallet to login to an API server with a pattern like:
GET message: String (from API server)
sign message with privateKey
POST signature (to API server)
verify signature with stored publicKey
I've attempted to use nodeJS crypto.verify to decode the signed message on the API side but am a bit out of my depth digging into Buffers and elliptic curves:
// Front-end code
const toHexString = (buffer: Buffer) =>
buffer.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
const data = new TextEncoder().encode('message to verify');
const signed = await wallet.sign(data, "hex");
await setLogin({ // sends API post call to backend
variables: {
publicAddress: walletPublicKey,
signature: toHexString(signed.signature),
},
});
// Current WIP for backend code
const ALGORITHM = "ed25519";
const fromHexString = (hexString) =>
new Uint8Array(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const signature = fromHexString(args.signature);
const nonceUint8 = new TextEncoder().encode('message to verify');
const verified = crypto.verify(
ALGORITHM,
nonceUint8,
`-----BEGIN PUBLIC KEY-----\n${user.publicAddress}\n-----END PUBLIC KEY-----`,
signature
);
console.log("isVerified: ", verified);
I'm pretty sure I'm going about this the wrong way and there must be an obvious method I'm missing.
As the space matures I expect a verify function or lib will appear to to consume the output of const signed = await wallet.sign(data, "hex");
Something like:
import { VerifyMessage } from '#solana/web3.js';
const verified = VerifyMessage(message, publicKey, signature, 'hex');
But after 3 days of pushing hard I'm starting to hit my limits and my brain is failing. Any help or direction where to look much appreciated 🙏
Solved with input from the fantastic Project Serum discord devs. High level solution is to use libs that are also used in the sol-wallet-adapter repo, namely tweetnacl and bs58:
const signatureUint8 = base58.decode(args.signature);
const nonceUint8 = new TextEncoder().encode(user?.nonce);
const pubKeyUint8 = base58.decode(user?.publicAddress);
nacl.sign.detached.verify(nonceUint8, signatureUint8, pubKeyUint8)
// true
I recommend staying in solana-labs trail and use tweetnacl
spl-token-wallet (sollet.io) signs an arbitrary message with
nacl.sign.detached(message, this.account.secretKey)
https://github.com/project-serum/spl-token-wallet/blob/9c9f1d48a589218ffe0f54b7d2f3fb29d84f7b78/src/utils/walletProvider/localStorage.js#L65-L67
on the other end, verify is done with
nacl.sign.detached.verify
in #solana/web3.js
https://github.com/solana-labs/solana/blob/master/web3.js/src/transaction.ts#L560
Use nacl.sign.detached.verify in your backend and you should be good. I also recommend avoiding any data format manipulation, I am not sure what you were trying to do but if you do verify that each step is correct.
For iOS, solana.request will cause error. Use solana.signMessage and base58 encode the signature.
var _signature = '';
try {
signedMessage = await window.solana.request({
method: "signMessage",
params: {
message: encodedMessage
},
});
_signature = signedMessage.signature;
} catch (e) {
try {
signedMessage = await window.solana.signMessage(encodedMessage);
_signature = base58.encode(signedMessage.signature);
} catch (e1) {
alert(e1.message);
}
}
//
try {
signIn('credentials',
{
publicKey: signedMessage.publicKey,
signature: _signature,
callbackUrl: `${window.location.origin}/`
}
)
} catch (e) {
alert(e.message);
}
I needed to convert Uint8Array to string and convert it back to Uint8Array for HTTP communication. I found the toLocaleString method of Uint8Array helpful in this case. It outputs comma-separated integers as a string.
const signedMessage = await window.solana.signMessage(encodedMessage, "utf8");
const signature = signedMessage.signature.toLocaleString();
And then you can convert it back to Uint8Array with the following code.
const signatureUint8 = new Uint8Array(signature.split(",").map(Number));
Edit
The solution above was working on the desktop but when I tried my code inside the Phantom wallet iOS browser it gave an error. I guess the toLocaleString method is not available in that browser. I found a more solid solution to convert Uint8Array to a comma-separated string
Array.apply([], signedMessage.signature).join(",")
Signing and base64 encode:
const data = new TextEncoder().encode(message);
const signature = await wallet.signMessage(data); // Uint8Array
const signatureBase64 = Buffer.from(signature).toString('base64')
Base64 decode and verifying:
const signatureUint8 = new Uint8Array(atob(signature).split('').map(c => c.charCodeAt(0)))
const messageUint8 = new TextEncoder().encode(message)
const pubKeyUint8 = wallet.publicKey.toBytes() // base58.decode(publicKeyAsString)
const result = nacl.sign.detached.verify(messageUint8, signatureUint8, pubKeyUint8) // true or false
Full code example: https://github.com/enginer/solana-message-sign-verify-example

Decrypt JWT Token in C#

Hello I'm trying to see if they're any JWT token library similar to JOSE-JWT that also works on a Linux machine during runtime.
This is what I currently have in code that decrypts a token and grabs what I need.
private IamTokenDecrypted DecryptToken(string idToken)
{
byte[] key = WebEncoders.Base64UrlDecode(_ssoEncryptionKeyInfo.JsonWebKey.K);
string json = Jose.JWT.Decode(idToken, key, JweAlgorithm.DIR, JweEncryption.A256GCM);
var result = Jose.JWT.Payload(json);
var iamTokenDecrypted = JsonConvert.DeserializeObject<IamTokenDecrypted>(result);
return iamTokenDecrypted;
}
I have a security key and and as you see it has some encryption in it

jsonwebtoken package fails to verify user Office.context.mailbox.getUserIdentityToken result

I'm developing an Outlook web add-in.
I'm trying to verify a token that I'm passing to server side, through a node.js library, but it's failing and I can't understand why.
This is what I'm doing to retrieve the user identity token.
Office.context.mailbox.getUserIdentityTokenAsync(function(result) {
result.value // contains the token.
// send this value to server side, which I can see that it's working.
})
On the server side, I retrieve the token and do below:
token; // contains the token passed from the web-app.
const jwt = require('jsonwebtoken');
const request = require('request');
let decoded = jwt.decode(token, {complete: true});
// Get the key, we'll need this later since we'll have to
// find the matching key from the config file.
let key = decoded.header.x5t;
let amurl = JSON.parse(decoded.payload.appctx).amurl;
// Make a request to the url to get the configuration json file.
request(amurl, {}, (err, response, body) => {
let keys = JSON.parse(body).keys;
// Filter the keys so we get the one which we can verify.
let s = keys.filter(t => t.keyinfo.x5t === key);
let cert = s[0].keyvalue.value;
// Fails the verification.
console.log(jwt.verify(token, cert));
});
Just to clarify, I'm retrieving the token correctly and this npm package seems to be functioning fine for other jwt tokens. (So it's not really a configuration issue)
I have now found the answer to this question.
Just to re-iterate the problem was:
Office.context.mailbox.getUserIdentityToken method returns a jwt token.
When decoded this token contains an amurl field which points to the public certificate as a text.
When jsonwebtoken.verify(token, certText) is called, it's failing with the message invalid algorithm (even if you specify the algorithm from the token's header)
The problem was the formatting of the certificate text. jsonwebtoken package was looking for a particular formatting (splitted with 64 characters across each line along with the certificate begin and certificate end lines, so when formatted with method below - it started working properly.
The original code is taken from here: https://github.com/auth0/node-jsonwebtoken/issues/68 and formatted slightly to fit the needs.
/**
* #param {string} key - The certificate value retrieved from amurl property.
*/
formatKey: function(key) {
const beginKey = "-----BEGIN CERTIFICATE-----";
const endKey = "-----END CERTIFICATE-----";
const sanitizedKey = key.replace(beginKey, '').replace(endKey, '').replace('\n', '')
const keyArray = sanitizedKey.split('').map((l, i) => {
const position = i + 1
const isLastCharacter = sanitizedKey.length === position
if(position % 64 === 0 || isLastCharacter) {
return l + '\n'
}
return l
})
return `${beginKey}\n${keyArray.join('')}${endKey}\n`
}

Axios Encrypted POST parameters

I want to use the API from bitgrail (docs: https://bitgrail.com/api-documentation). And request the amount of balances. To do that you have to set a SIGNATURE which includes the encrypted post parameters using HMAC-SHA512 with ur API-Secret.
So you have to sent this data:
Header:
KEY - Public API key
SIGNATURE - encrypted POST parameters with HMAC-SHA512 alghoritm using your secret API key
Data:
nonce - Integer number, always greater then nonce of previous call.
But everytime I try to send the request I get an 'Authentication failed'-Error from Bitgrail.
The params are set like so:
params = {}
params.nonce = n();
and then encrypted like this:
let hmac = crypto.createHmac('sha512', 'MYSECRET');
let digest = hmac.update(params.toString()).digest('hex');
let signature = new Buffer(digest).toString('base64');
Maybe the 'params.toString()' is not working. Do I have to set the params variable as an array?
I figured it our by myself by using const { URLSearchParams } = require('url');
and deleting this line: let signature = new Buffer(digest).toString('base64'); and just using the digest as signature.