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`
}
Related
I need to execute an external function at a deployed smart contract. This function receives a "Signature" struct as a parameter.
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
I've checked at https://medium.com/#angellopozo/ethereum-signing-and-validating-13a2d7cb0ee3
, the following code:
const Web3 = require('web3')
const provider = new Web3.providers.HttpProvider('http://localhost:8545')
const web3 = new Web3(provider)
function toHex(str) {
var hex = ''
for(var i=0;i<str.length;i++) {
hex += ''+str.charCodeAt(i).toString(16)
}
return hex
}
let addr = web3.eth.accounts[0]
let msg = 'I really did make this message'
let signature = web3.eth.sign(addr, '0x' + toHex(msg))
console.log(signature)
What should we set at "msg" in this case to call the function?
The article you shared is about verifying the account which does the transaction:
Signing is a nice way to know something is being done by the correct
person/contract. This means we can trust that someone is actually
doing what they say they are.
Especially in NFT marketplaces before you upload metadata or images you are required to verify that your metamask account will the one who really does the transactions. To verify you just sign a message on the front-end and sent this signed message to the server. This msg can be anything, its value is not important, its signature is important. Once you receive the msg, you can verify it. I will show you the verification process, so you will extract the v,r,s and then pass them to your contract function if the verification process is successful. You should be also building the contract on the server as well.
import * as util from "ethereumjs-util";
import { ethers } from "ethers";
// you could send this with session as well
const message = req.body.message;
// getting contract on serverside. I am setting with ganache
const provider = new ethers.providers.JsonRpcProvider(
"http://127.0.0.1:7545"
);
// import your abi and deployed smart contract address
const contract = new ethers.Contract(
contractAddress,
abi,
provider
)
// ****** Verification Process Starts ********
// we need to get the unsigned message
// nonce is the representation of something that we are going to sign
let nonce: string | Buffer =
"\x19Ethereum Signed Message:\n" +
JSON.stringify(message).length +
JSON.stringify(message);
// create a new buffer
nonce = util.keccak(Buffer.from(nonce, "utf-8"));
// signature: signedData, from signature we will get the address
const { v, r, s } = util.fromRpcSig(req.body.signature);
// matching signature with the unsigned message
const pubKey = util.ecrecover(util.toBuffer(nonce), v, r, s);
const addressBuffer = util.pubToAddress(pubKey);
const address = util.bufferToHex(addressBuffer);
if (address === req.body.address) {
// here call your contract external address passing "v,r,s"
} else {
reject("Validation Failed");
}
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?
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
In AppSync, when you use Cognito User Pools as your auth setting your identity you get
identity:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
issuer: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
username: 'skillet',
claims:
{ sub: 'bcb5cd53-315a-40df-a41b-1db02a4c1bd9',
aud: '7re1oap5fhm3ngpje9r81vgpoe',
email_verified: true,
event_id: 'bb65ba5d-4689-11e8-bee7-2d0da8da81ab',
token_use: 'id',
auth_time: 1524441800,
iss: 'https://cognito-idp.us-west-2.amazonaws.com/us-west-2_oicu812',
'cognito:username': 'skillet',
exp: 1524459387,
iat: 1524455787,
email: 'myemail#nope.com' },
sourceIp: [ '11.222.33.200' ],
defaultAuthStrategy: 'ALLOW',
groups: null }
However when you use AWS_IAM auth you get
identity:
{ accountId: '12121212121', //<--- my amazon account ID
cognitoIdentityPoolId: 'us-west-2:39b1f3e4-330e-40f6-b738-266682302b59',
cognitoIdentityId: 'us-west-2:a458498b-b1ac-46c1-9c5e-bf932bad0d95',
sourceIp: [ '33.222.11.200' ],
username: 'AROAJGBZT5A433EVW6O3Q:CognitoIdentityCredentials',
userArn: 'arn:aws:sts::454227793445:assumed-role/MEMORYCARDS-CognitoAuthorizedRole-dev/CognitoIdentityCredentials',
cognitoIdentityAuthType: 'authenticated',
cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7"' }
The Docs says that this is expected, https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html .
However, if you use AWS_IAM connected to Cognito (which is required to have unauthenticated access), how are you supposed to get at the User's username, email, sub, etc? I need access to the user's claims when using AWS_IAM type Auth.
For making User's username, email, sub etc. accessible through AppSync API, there's an answer for that: https://stackoverflow.com/a/42405528/1207523
To sum it up, you want to send User Pools ID token to your API (e.g. AppSync or API Gateway). Your API request is IAM authenticated. Then you validate the ID token in a Lambda function and now you have your validated IAM user and User Pools data together.
You want to use the IAM's identity.cognitoIdentityId as primary key for you User table. Add the data included in ID token (username, email, etc.) as attributes.
This way you can make user's claims available through you API. Now, for example, you can set $ctx.identity.cognitoIdentityId as the owner of an item. Then maybe other users can see the name of the owner via GraphQL resolvers.
If you need to access the user's claims in your resolver I'm afraid that doesn't seems to be possible at the moment. I have made a question about this as it would be very helpful for authorization: Group authorization in AppSync using IAM authentication
In this case, instead of using a resolver you could use Lambda as a data source and retrieve the user's claims from the above-mentioned User table.
It's all a bit difficult at the moment :)
Here is bad answer that works. I notice that cognitoIdentityAuthProvider: '"cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob","cognito-idp.us-west-2.amazonaws.com/us-west-2_HighBob:CognitoSignIn:1a072f08-5c61-4c89-807e-417d22702eb7" contains the Cognito user's sub (the big after CognitoSignIn). You can extract that with a regex and use the aws-sdk to get the user's info from cognito user pool.
///////RETRIEVE THE AUTHENTICATED USER'S INFORMATION//////////
if(event.context.identity.cognitoIdentityAuthType === 'authenticated'){
let cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
//Extract the user's sub (ID) from one of the context indentity fields
//the REGEX in match looks for the strings btwn 'CognitoSignIn:' and '"', which represents the user sub
let userSub = event.context.identity.cognitoIdentityAuthProvider.match(/CognitoSignIn:(.*?)"/)[1];
let filter = 'sub = \"'+userSub+'\"' // string with format = 'sub = \"1a072f08-5c61-4c89-807e-417d22702eb7\"'
let usersData = await cognitoidentityserviceprovider.listUsers( {Filter: filter, UserPoolId: "us-west-2_KsyTKrQ2M",Limit: 1}).promise()
event.context.identity.user=usersData.Users[0];
}
It's a bad answer because you are pinging the User Pool database instead of just decoding a JWT.
Here is my answer. There was a bug in the appSync client library that would overwrite all custom headers. That has since been fixed. Now you can pass down custom headers that will make it all the way to you resolvers, which I pass to my lambda functions (again, note I am using lambda datasourcres and not using dynamoDB).
So I attach my logged in JWT on the client side and, server side in my lambda function, I decode it. You need the public key created by cognito to validate the JWT. (YOU DO NOT NEED A SECRET KEY.) There is a "well known key" url associated with every user pool which I ping the first time my lambda is spun up but, just like my mongoDB connection, it is persisted between lambda calls (at least for a while.)
Here is lambda resolver...
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const jwkToPem = require('jwk-to-pem');
const request = require('request-promise-native');
const _ = require('lodash')
//ITEMS THAT SHOULD BE PERSISTED BETWEEN LAMBDA EXECUTIONS
let conn = null; //MONGODB CONNECTION
let pem = null; //PROCESSED JWT PUBLIC KEY FOR OUR COGNITO USER POOL, SAME FOR EVERY USER
exports.graphqlHandler = async (event, lambdaContext) => {
// Make sure to add this so you can re-use `conn` between function calls.
// See https://www.mongodb.com/blog/post/serverless-development-with-nodejs-aws-lambda-mongodb-atlas
lambdaContext.callbackWaitsForEmptyEventLoop = false;
try{
////////////////// AUTHORIZATION/USER INFO /////////////////////////
//ADD USER INFO, IF A LOGGED IN USER WITH VALID JWT MAKES THE REQUEST
var token = _.get(event,'context.request.headers.jwt'); //equivalen to "token = event.context.re; quest.headers.alexauthorization;" but fails gracefully
if(token){
//GET THE ID OF THE PUBLIC KEY (KID) FROM THE TOKEN HEADER
var decodedToken = jwt.decode(token, {complete: true});
// GET THE PUBLIC KEY TO NEEDED TO VERIFY THE SIGNATURE (no private/secret key needed)
if(!pem){
await request({ //blocking, waits for public key if you don't already have it
uri:`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`,
resolveWithFullResponse: true //Otherwise only the responce body would be returned
})
.then(function ( resp) {
if(resp.statusCode != 200){
throw new Error(resp.statusCode,`Request of JWT key with unexpected statusCode: expecting 200, received ${resp.statusCode}`);
}
let {body} = resp; //GET THE REPSONCE BODY
body = JSON.parse(body); //body is a string, convert it to JSON
// body is an array of more than one JW keys. User the key id in the JWT header to select the correct key object
var keyObject = _.find(body.keys,{"kid":decodedToken.header.kid});
pem = jwkToPem(keyObject);//convert jwk to pem
});
}
//VERIFY THE JWT SIGNATURE. IF THE SIGNATURE IS VALID, THEN ADD THE JWT TO THE IDENTITY OBJECT.
jwt.verify(token, pem, function(error, decoded) {//not async
if(error){
console.error(error);
throw new Error(401,error);
}
event.context.identity.user=decoded;
});
}
return run(event)
} catch (error) {//catch all errors and return them in an orderly manner
console.error(error);
throw new Error(error);
}
};
//async/await keywords used for asynchronous calls to prevent lambda function from returning before mongodb interactions return
async function run(event) {
// `conn` is in the global scope, Lambda may retain it between function calls thanks to `callbackWaitsForEmptyEventLoop`.
if (conn == null) {
//connect asyncoronously to mongodb
conn = await mongoose.createConnection(process.env.MONGO_URL);
//define the mongoose Schema
let mySchema = new mongoose.Schema({
///my mongoose schem
});
mySchema('toJSON', { virtuals: true }); //will include both id and _id
conn.model('mySchema', mySchema );
}
//Get the mongoose Model from the Schema
let mod = conn.model('mySchema');
switch(event.field) {
case "getOne": {
return mod.findById(event.context.arguments.id);
} break;
case "getAll": {
return mod.find()
} break;
default: {
throw new Error ("Lambda handler error: Unknown field, unable to resolve " + event.field);
} break;
}
}
This is WAY better than my other "bad" answer because you are not always querying a DB to get info that you already have on the client side. About 3x faster in my experience.
If you are using AWS Amplify, what I did to get around this was to set a custom header username as explained here, like so:
Amplify.configure({
API: {
graphql_headers: async () => ({
// 'My-Custom-Header': 'my value'
username: 'myUsername'
})
}
});
then in my resolver I would have access to the header with:
$context.request.headers.username
As explained by the AppSync's docs here in the section Access Request Headers
Based on Honkskillets answer, I have written a lambda function that will return you the user attributes. You just supply the function with the JWT.
const jwt = require("jsonwebtoken");
const jwkToPem = require("jwk-to-pem");
const request = require("request-promise");
exports.handler = async (event, context) => {
try {
const { token } = event;
const decodedToken = jwt.decode(token, { complete: true });
const publicJWT = await request(
`https://cognito-idp.${process.env.REGION}.amazonaws.com/${process.env.USER_POOL_ID}/.well-known/jwks.json`
);
const keyObject = JSON.parse(publicJWT).keys.find(
key => key.kid == decodedToken.header.kid
);
const pem = jwkToPem(keyObject);
return {
statusCode: 200,
body: jwt.verify(token, pem)
};
} catch (error) {
console.error(error);
return {
statusCode: 500,
body: error.message
};
}
};
I use it in Appsync where I create Pipeline resolvers and add this function whenever I need user attributes. I supply the JWT by grabbing it from the header in the resolver using $context.request.
I would like to use Azure AD B2C but have several difficulties using it.
One problem I have is to validate the signature of the token.
First I wanted to validate the token "manually" using jwt.io.
According to the Microsoft Docs, validating the signature should work like this:
Your app can use the kid claim in the JWT header to select the public key in the JSON document that is used to sign a particular token. It can then perform signature validation by using the correct public key and the indicated algorithm.
My understandig: Grab the kid value out of the header, lookup the key in the metadata under the location of jwks_uri, (assumption) use the value of "n" to verify the signature.
But Jwt.io, jsonwebtoken.io, and jose-jwt all say, that the signature is invalid.
What am I missing?
Jwt.io seems to only support HS265 with a string secret and RS256 with a string secret or a certificate.
Azure AD B2C uses the more native form of RS256 which as per RFC 3447, section 3.1 defines that the public key consists of two components: n and e. The JWK contains both n and e which can be used to generate public key and validate the token signature.
In order to use Jwt.io, you'll need to convert Azure AD B2C's n + e format for the key to a cert format. See this example for a reference on how to do this: Go Language Convert Modulus exponent to X.509 certificate
There is now a way to validate tokens using two npm packages.
Resources
The information was made available at: Microsoft - Example-verifying-validation-tokens
Package Versions Used
jwks-rsa: "1.8.1"
jsonwebtoken: "8.5.1"
How To
The example below was modified from example in Microsoft - Example-verifying-validation-tokens
Retrieving "Issuer" and "User Flow" Name Values From Azure
Retrieving Application Id
Code Example
import jwt from 'jsonwebtoken';
import jkwsClient from 'jwks-rsa';
// Variables that need to be defined based on your Azure B2C Configuration.
const jwksUri = 'https://MY-B2C-TENANT.b2clogin.com/MY-B2C-TENANT.onmicrosoft.com/MY-USER-FLOW-NAME/discovery/v2.0/keys';
const client = jkwsClient({
jwksUri
});
export function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
var signingKey = key.getPublicKey();
callback(null, signingKey);
});
}
export function isTokenValid(token, applicationId, issuerUri) {
return new Promise((resolve) => {
const options = {
audience: [applicationId],
issuer: [issuerUri]
};
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
// eslint-disable-next-line no-console
console.error('Jwt Validation Failed', err);
resolve(false);
} else {
// eslint-disable-next-line no-console
console.debug(decoded)
resolve(true);
}
});
});
}
Usage
const applicationId = 'APPLICATION ID OF B2C TENANT'
const issuerUri = 'In the User Flow Properties'
const valid = isTokenValid('some token value without "Bearer", applicationId, issuerUri);
Notes
The jwksUri contains the user "User Policy". If you have multiple "User Policies", you will need to figure out how to instantiate the jkwsClient with correct jwksUri based on the each "User Policy" flow.
While you can leverage jwt.io or jwt.ms, a token can be decoded in the debugger console. This will give you some key information such as the "iss" (Issuer) and the "kid":
'e3R5cDogIkpXVCIsIGFsZzogIlJTMjU2Iiwga2lkOiAiWU9VX1NORUFLWV9SQUJCSVQifQ==.e2V4cDogMTU5NjYwMTc4NiwgbmJmOiAxNTk2NTk4MTg2LCB2ZXI6ICIxLjAiLCBpc3M6ICJodHRwczovL3doaXRlLXJhYmJpdC5iMmNsb2dpbi5jb20vZjIzNDZhMzBhLTk1ODEtNGU0ZC04MWQwLWQyZjk4NTQ3MWJhOS92Mi4wLyIsIHN1YjogImYzMmNjNmJhLWE5MTctNGE1Ni1hYjhmLWIyNGZmMTg1ODUyOCIsICIuLi4iOiAibW9yZSB2YWx1ZXMuLi4ifQ==.UNODECODEME'.split('.').map((value, index) => {
// The signature can't be decoded
if(index > 1) { return value; }
return atob(value);
});
You can use https://play.golang.org/p/7wWMBOp10R to get a public key from modulus(n) and exponent(e). Then copy the entire output in jwt.io and you'll see the signature is valid.