Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS Load Balancer Auth #514

Closed
delianides opened this issue Aug 23, 2018 · 12 comments
Closed

AWS Load Balancer Auth #514

delianides opened this issue Aug 23, 2018 · 12 comments

Comments

@delianides
Copy link

AWS recently added the functionality to authenticate a user on the load balancer and have a authenticated and hydrated user details in the request header.

I wasn't able to decode the object that comes from the load balancer even though it will decode on jwt.io. The example AWS give is in python but should be straight forward enough to decode the token.

https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-authenticate-users.html

Has anyone attempted to decode the x-amzn-oidc-data header using jwt.decode?

@MitMaro
Copy link
Contributor

MitMaro commented Aug 23, 2018

Interesting!

Could you provide the code that you tried that did not work?

Off the top of my head it should be pretty easy, and would be something along the lines of:

// this should always work
console.log(jwt.decode(token));

const pubKey = fs.readFileSync('/path/to/publickey'); // replace this line with the HTTP request to get the key

jwt.verify(token, pubKey, (err, decoded) => {
  console.log('err:', err);
  console.log(decoded);
});

@delianides
Copy link
Author

@MitMaro

This is what I was trying to do but I've even tried your example and it still didn't work. The only thing I added was the { algorithms: [ 'ES256' ]}.

const verifyJwt = async (token, db) => {
  const encoded_jwt_header = token.split('.')[0];
  const decoded_jwt = JSON.parse(base64.decode(encoded_jwt_header));
  const request = await fetch(
    `https://public-keys.auth.elb.us-west-2.amazonaws.com/${decoded_jwt.kid}`,
  );
  const cert = await request.text();  // -----BEGIN PUBLIC KEY----...

  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      cert,
      { algorithms: ['ES256'] }, 
      async (err, decoded) => {
        if (err) {
          reject(err);
        }

        resolve(decoded);
      },
    );
  });
};

@MitMaro
Copy link
Contributor

MitMaro commented Aug 24, 2018

Could you provide the output/error that occurs from the code you have above?

For reference, the following is an example the works with a ECDSA + P-256 + SHA256 public/private pair, partially taken from the tests:

'use strict';

const jwt = require('jsonwebtoken');

const es256PrivateKey = '-----BEGIN EC PARAMETERS-----\n' +
  'MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////////\n' +
  '/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6\n' +
  'k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+\n' +
  'kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK\n' +
  'fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz\n' +
  'ucrC/GMlUQIBAQ==\n' +
  '-----END EC PARAMETERS-----\n' +
  '-----BEGIN EC PRIVATE KEY-----\n' +
  'MIIBaAIBAQQgeg2m9tJJsnURyjTUihohiJahj9ETy3csUIt4EYrV+J2ggfowgfcC\n' +
  'AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////\n' +
  'MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr\n' +
  'vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE\n' +
  'axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W\n' +
  'K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8\n' +
  'YyVRAgEBoUQDQgAEEWluurrkZECnq27UpNauq16f9+5DDMFJZ3HV43Ujc3tcXQ++\n' +
  'N1T/0CAA8ve286f32s7rkqX/pPokI/HBpP5p3g==\n' +
  '-----END EC PRIVATE KEY-----\n';

const es256PublicKey = '-----BEGIN PUBLIC KEY-----\n' +
  'MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAA\n' +
  'AAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////\n' +
  '///////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSd\n' +
  'NgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5\n' +
  'RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA\n' +
  '//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABBFpbrq65GRAp6tu1KTWrqte\n' +
  'n/fuQwzBSWdx1eN1I3N7XF0PvjdU/9AgAPL3tvOn99rO65Kl/6T6JCPxwaT+ad4=\n' +
  '-----END PUBLIC KEY-----\n';

async function main() {
  const token = await new Promise((resolve, reject) => {
    jwt.sign({foo: 'bar'}, es256PrivateKey,  { algorithm: 'ES256'}, (e, t) => {
      if (e) {
        return reject(e);
      }
      resolve(t);
    });
  });

  console.log('token:', token);

  console.log('decoded:', jwt.decode(token, {complete: true}));

  const verified = await new Promise((resolve, reject) => {
    jwt.verify(
      token,
      es256PublicKey,
      { algorithms: ['ES256'] },
      async (err, decoded) => {
        if (err) {
          reject(err);
        }

        resolve(decoded);
      },
    );
  });

  console.log('verified:', verified);
}

main()
  .catch((err) => console.log(err))
;

@delianides
Copy link
Author

@MitMaro

Same data added to jwt.io

@delianides
Copy link
Author

@MitMaro figured it out and found this:
auth0/node-jws#84

@MitMaro
Copy link
Contributor

MitMaro commented Sep 11, 2018

That's a common mistake but Amazon should know better. Should be easy to do a simple string replace to work around the problem.

@jreeter
Copy link

jreeter commented Sep 20, 2018

@delianides @MitMaro Could you guys clarify the solution a bit more? What exactly are you string replacing the non url encoded bits?

@MitMaro
Copy link
Contributor

MitMaro commented Sep 20, 2018

@jreeter ,

Amazon is using base64 to encode the tokens, which has a different character set than base64url, which is what the JWT specification requires.

Specifically, the + character is replaced with -, the / character is replaced with _ and the trailing =s are optional. You can do a basic string replace on the invalid tokens to make them valid base64url encoded tokens.

Untested Code, but the basic idea would be:

const correctToken = token.replace(/+/g, '-').replace(/\//g, '_');

@msmesnik
Copy link

Just wanted to add that we're having the same issue. Converting token encoding to base64url will allow the library to decode it, however, it will also cause signature verification to fail (both with the library as well as on jwt.io, while using the original token and pubkey will successfully validate on jwt.io).

@morganabel
Copy link

@daerion I found a way to make this work, but it's not pretty......

Basically, the verify method in this library won't work, but the signature can be verified using the underlying node-jwa library. Then you just have to check things like is the token still valid (I am only checking if token is not expired):

const base64Url = require("base64url");
const jwt = require("jsonwebtoken");
const jws = require("jws");
const fetch = require("node-fetch").default;

async function verifyToken(token) {
  var base64UrlToken = base64Url.fromBase64(token);
  const decoded = jwt.decode(base64UrlToken, { complete: true });

  const { kid, signer } = decoded.header;
  const region = signer.split(":")[3];

  const uri = `https://public-keys.auth.elb.${region}.amazonaws.com/${kid}`;

  console.log(`Fetching key at: ${uri}`);

  const response = await fetch(uri);
  const key = await response.text();

  console.log(key);

  try {
    const verify = jws.verify(token, "ES256", key);
    if (!verify) {
      return null;
    }

    var clockTimestamp = Math.floor(Date.now() / 1000);
    if (clockTimestamp >= decoded.header.exp) {
      // Token expired.
      return null;
    }
  } catch (err) {
    console.error(err);
    throw err;
  }

  return decoded.payload;
}

@msmesnik
Copy link

msmesnik commented Feb 7, 2019

@morganabel Thanks for sharing your implementation :)

@trallnag
Copy link

trallnag commented Jul 6, 2021

Just ran into the same issue...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants