Skip to content

Commit

Permalink
fix(jwt): sign tokens with real rsa key
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Apr 13, 2020
1 parent 2c6306a commit 949d3fc
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"lint-staged": {
"*.ts": [
"eslint --fix",
"tsc --esModuleInterop --noEmit",
"tsc --esModuleInterop --resolveJsonModule --noEmit",
"prettier --write"
]
},
Expand Down
17 changes: 17 additions & 0 deletions src/keys/cognitoLocal.private.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"jwk": {
"p": "-7Wp_s52Z6Bj5Lggy7ps8LNIFvwFeZkFffT13lsShMUApkEufYsFotqVR7NdUh8eZ2ES-dE_qCo3TRLWe93BKAWlqZ3LbPXCAaQ6hYVk7g-g0KogXG73zY0wnyg5hAU-PMPKiKwbqIC63QzAU7Tn-neiL807BrN4MUQbnLmODU8",
"kty": "RSA",
"q": "3p3rC13eOFSaGy306cFzpApgEwT4UXvT6DCUL3EJZVyDWwyPIuQwiuyso8yJCq210TFDEne6ReZhHEM53T56_17AuXDfo4_xGvWfuVZLRHtS0mxjRzqSoepL76s-ux1jFmRuz_b-FlkR6P-QeW_AFcSoWLycjARQgO2jOfHPWWM",
"d": "TowhslUUsx2bl9fqjV-aOxgnktiSEogBLtU4HJ7dKc3hyzv20YTg09qc__kNZXP4twdxfOgpXBoFDSYZLDdZ6aQGEbG-fktQbH2Ys-LfuNgeI7OroHLnNoWx-fTY6A1Q8nYux8zyRCTGas0F4i3WM9bsN5tYw2QY25w2dTh08yjvp59vGTuiJyjQFhURpXwzIOQ_CtV7FayJhwJD_MaoN8qbquBeDjy2p20YsFwfLrlWSr8bRT54WVAqO4Mu8kG0Q-FGdU9bMDBTvPGAaxr8qzgXXWcoXEM-ByOjsXAcW6m7hhey2vzhfJ-2sSAiLHsQfcALwCuueNxZNzrdZgToyQ",
"e": "AQAB",
"use": "sig",
"kid": "CognitoLocal",
"qi": "Lg-HzxIdHjWkjjbaOba-TnYbixi1Sn9jFGQXJaovzl8nQaNF_qiTNK3yIYBz4cVp9gXfn8FA_kUIMZxq1romb6zp3W2sZkcRD2D40z63pRBkdGknwebSmdy9PnwfqONAFKzgJX514vGhM-e7gfoIrUayiVG-fNG2h4nXR1qnUTw",
"dp": "U3hx0DrdTw4EMmPRFF5VJBj_7gdTNXjGNnfWVQ90e6zswzVYWm-QxemgmW9koggJyBSL-2Ylqvmc7yUxFVB7bm84-Z-HRzHUTUEN2xtaVgu-s5PHOX_fEz4gApePQzWN5w6yilIwtddCoG1LFjcmuouTsDBpw5YeZJAGbBmofsc",
"alg": "RS256",
"dq": "ZNIebkJv7xEZzi9tGSTc67ErO9HnaHftS94cbrQB7l8MuoKgnMu91F1F_tUWR7jOfFSULNv-h8PDvVoQ7ctrRxaxsAqXrmr1ZiFR2k1jvzsfEl-2Qr8bQ6tqAryKp5Gym6SWrycMgjCKtPxxgR4EX5d2KuIZACzADPQTFZ4XK0M",
"n": "2uLO7yh1_6Icfd89V3nNTc_qhfpDN7vEmOYlmJQlc9_RmOns26lg88fXXFntZESwHOm7_homO2Ih6NOtu4P5eskGs8d8VQMOQfF4YrP-pawVz-gh1S7eSvzZRDHBT4ItUuoiVP1B9HN_uScKxIqjmitpPqEQB_o2NJv8npCfqUAU-4KmxquGtjdmfctswSZGdz59M3CAYKDfuvLH9_vV6TRGgbUaUAXWC2WJrbbEXzK3XUDBrmF3Xo-yw8f3SgD3JOPl3HaaWMKL1zGVAsge7gQaGiJBzBurg5vwN61uDGGz0QZC1JqcUTl3cZnrx_L8isIR7074SJEuljIZRnCcjQ"
},
"pem": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEA2uLO7yh1/6Icfd89V3nNTc/qhfpDN7vEmOYlmJQlc9/RmOns\n26lg88fXXFntZESwHOm7/homO2Ih6NOtu4P5eskGs8d8VQMOQfF4YrP+pawVz+gh\n1S7eSvzZRDHBT4ItUuoiVP1B9HN/uScKxIqjmitpPqEQB/o2NJv8npCfqUAU+4Km\nxquGtjdmfctswSZGdz59M3CAYKDfuvLH9/vV6TRGgbUaUAXWC2WJrbbEXzK3XUDB\nrmF3Xo+yw8f3SgD3JOPl3HaaWMKL1zGVAsge7gQaGiJBzBurg5vwN61uDGGz0QZC\n1JqcUTl3cZnrx/L8isIR7074SJEuljIZRnCcjQIDAQABAoIBAE6MIbJVFLMdm5fX\n6o1fmjsYJ5LYkhKIAS7VOBye3SnN4cs79tGE4NPanP/5DWVz+LcHcXzoKVwaBQ0m\nGSw3WemkBhGxvn5LUGx9mLPi37jYHiOzq6By5zaFsfn02OgNUPJ2LsfM8kQkxmrN\nBeIt1jPW7DebWMNkGNucNnU4dPMo76efbxk7oico0BYVEaV8MyDkPwrVexWsiYcC\nQ/zGqDfKm6rgXg48tqdtGLBcHy65Vkq/G0U+eFlQKjuDLvJBtEPhRnVPWzAwU7zx\ngGsa/Ks4F11nKFxDPgcjo7FwHFupu4YXstr84XyftrEgIix7EH3AC8ArrnjcWTc6\n3WYE6MkCgYEA+7Wp/s52Z6Bj5Lggy7ps8LNIFvwFeZkFffT13lsShMUApkEufYsF\notqVR7NdUh8eZ2ES+dE/qCo3TRLWe93BKAWlqZ3LbPXCAaQ6hYVk7g+g0KogXG73\nzY0wnyg5hAU+PMPKiKwbqIC63QzAU7Tn+neiL807BrN4MUQbnLmODU8CgYEA3p3r\nC13eOFSaGy306cFzpApgEwT4UXvT6DCUL3EJZVyDWwyPIuQwiuyso8yJCq210TFD\nEne6ReZhHEM53T56/17AuXDfo4/xGvWfuVZLRHtS0mxjRzqSoepL76s+ux1jFmRu\nz/b+FlkR6P+QeW/AFcSoWLycjARQgO2jOfHPWWMCgYBTeHHQOt1PDgQyY9EUXlUk\nGP/uB1M1eMY2d9ZVD3R7rOzDNVhab5DF6aCZb2SiCAnIFIv7ZiWq+ZzvJTEVUHtu\nbzj5n4dHMdRNQQ3bG1pWC76zk8c5f98TPiACl49DNY3nDrKKUjC110KgbUsWNya6\ni5OwMGnDlh5kkAZsGah+xwKBgGTSHm5Cb+8RGc4vbRkk3OuxKzvR52h37UveHG60\nAe5fDLqCoJzLvdRdRf7VFke4znxUlCzb/ofDw71aEO3La0cWsbAKl65q9WYhUdpN\nY787HxJftkK/G0OragK8iqeRspuklq8nDIIwirT8cYEeBF+XdiriGQAswAz0ExWe\nFytDAoGALg+HzxIdHjWkjjbaOba+TnYbixi1Sn9jFGQXJaovzl8nQaNF/qiTNK3y\nIYBz4cVp9gXfn8FA/kUIMZxq1romb6zp3W2sZkcRD2D40z63pRBkdGknwebSmdy9\nPnwfqONAFKzgJX514vGhM+e7gfoIrUayiVG+fNG2h4nXR1qnUTw=\n-----END RSA PRIVATE KEY-----"
}
11 changes: 11 additions & 0 deletions src/keys/cognitoLocal.public.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"jwt": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "CognitoLocal",
"alg": "RS256",
"n": "2uLO7yh1_6Icfd89V3nNTc_qhfpDN7vEmOYlmJQlc9_RmOns26lg88fXXFntZESwHOm7_homO2Ih6NOtu4P5eskGs8d8VQMOQfF4YrP-pawVz-gh1S7eSvzZRDHBT4ItUuoiVP1B9HN_uScKxIqjmitpPqEQB_o2NJv8npCfqUAU-4KmxquGtjdmfctswSZGdz59M3CAYKDfuvLH9_vV6TRGgbUaUAXWC2WJrbbEXzK3XUDBrmF3Xo-yw8f3SgD3JOPl3HaaWMKL1zGVAsge7gQaGiJBzBurg5vwN61uDGGz0QZC1JqcUTl3cZnrx_L8isIR7074SJEuljIZRnCcjQ"
},
"pem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA2uLO7yh1/6Icfd89V3nNTc/qhfpDN7vEmOYlmJQlc9/RmOns26lg\n88fXXFntZESwHOm7/homO2Ih6NOtu4P5eskGs8d8VQMOQfF4YrP+pawVz+gh1S7e\nSvzZRDHBT4ItUuoiVP1B9HN/uScKxIqjmitpPqEQB/o2NJv8npCfqUAU+4KmxquG\ntjdmfctswSZGdz59M3CAYKDfuvLH9/vV6TRGgbUaUAXWC2WJrbbEXzK3XUDBrmF3\nXo+yw8f3SgD3JOPl3HaaWMKL1zGVAsge7gQaGiJBzBurg5vwN61uDGGz0QZC1Jqc\nUTl3cZnrx/L8isIR7074SJEuljIZRnCcjQIDAQAB\n-----END RSA PUBLIC KEY-----"
}
85 changes: 82 additions & 3 deletions src/targets/initiateAuth.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { advanceTo } from "jest-date-mock";
import * as uuid from "uuid";
import {
InvalidPasswordError,
NotAuthorizedError,
Expand All @@ -8,14 +10,21 @@ import { UserPool } from "../services";
import { Triggers } from "../services/triggers";
import { InitiateAuth, InitiateAuthTarget } from "./initiateAuth";
import jwt from "jsonwebtoken";
import PublicKey from "../keys/cognitoLocal.public.json";

const UUID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;

describe("InitiateAuth target", () => {
let initiateAuth: InitiateAuthTarget;
let mockDataStore: jest.Mocked<UserPool>;
let mockCodeDelivery: jest.Mock;
let mockTriggers: jest.Mocked<Triggers>;
let now: Date;

beforeEach(() => {
now = new Date(2020, 1, 2, 3, 4, 5);
advanceTo(now);

mockDataStore = {
getUserByUsername: jest.fn(),
getUserPoolIdForClientId: jest.fn(),
Expand Down Expand Up @@ -183,14 +192,18 @@ describe("InitiateAuth target", () => {

expect(decodedAccessToken).toMatchObject({
client_id: "clientId",
iss: "http://localhost:9229/user-pool-id",
iss: "http://localhost:9229/userPoolId",
sub: "0000-0000",
token_use: "access",
username: "0000-0000",
event_id: expect.stringMatching(UUID),
scope: "aws.cognito.signin.user.admin", // TODO: scopes
auth_time: now.getTime(),
jti: expect.stringMatching(UUID),
});
});

it("generates an id token", async () => {
it("generates an access token that's verifiable with our public key", async () => {
mockDataStore.getUserPoolIdForClientId.mockResolvedValue("userPoolId");
mockDataStore.getUserByUsername.mockResolvedValue({
Attributes: [],
Expand All @@ -211,18 +224,84 @@ describe("InitiateAuth target", () => {
},
});

expect(output).toBeDefined();
expect(output.AuthenticationResult.AccessToken).toBeDefined();

expect(
jwt.verify(output.AuthenticationResult.AccessToken!, PublicKey.pem, {
algorithms: ["RS256"],
})
).toBeTruthy();
});

it("generates an id token", async () => {
mockDataStore.getUserPoolIdForClientId.mockResolvedValue("userPoolId");
mockDataStore.getUserByUsername.mockResolvedValue({
Attributes: [{ Name: "email", Value: "[email protected]" }],
UserStatus: "CONFIRMED",
Password: "hunter2",
Username: "0000-0000",
Enabled: true,
UserCreateDate: new Date().getTime(),
UserLastModifiedDate: new Date().getTime(),
});

const output = await initiateAuth({
ClientId: "clientId",
AuthFlow: "USER_PASSWORD_AUTH",
AuthParameters: {
USERNAME: "0000-0000",
PASSWORD: "hunter2",
},
});

expect(output).toBeDefined();
expect(output.AuthenticationResult.IdToken).toBeDefined();

const decodedIdToken = jwt.decode(output.AuthenticationResult.IdToken!);

expect(decodedIdToken).toMatchObject({
aud: "clientId",
iss: "http://localhost:9229/user-pool-id",
iss: "http://localhost:9229/userPoolId",
sub: "0000-0000",
token_use: "id",
"cognito:username": "0000-0000",
email_verified: true,
event_id: expect.stringMatching(UUID),
auth_time: now.getTime(),
email: "[email protected]",
});
});

it("generates an id token verifiable with our public key", async () => {
mockDataStore.getUserPoolIdForClientId.mockResolvedValue("userPoolId");
mockDataStore.getUserByUsername.mockResolvedValue({
Attributes: [{ Name: "email", Value: "[email protected]" }],
UserStatus: "CONFIRMED",
Password: "hunter2",
Username: "0000-0000",
Enabled: true,
UserCreateDate: new Date().getTime(),
UserLastModifiedDate: new Date().getTime(),
});

const output = await initiateAuth({
ClientId: "clientId",
AuthFlow: "USER_PASSWORD_AUTH",
AuthParameters: {
USERNAME: "0000-0000",
PASSWORD: "hunter2",
},
});

expect(output).toBeDefined();
expect(output.AuthenticationResult.IdToken).toBeDefined();

expect(
jwt.verify(output.AuthenticationResult.IdToken!, PublicKey.pem, {
algorithms: ["RS256"],
})
).toBeTruthy();
});

it.todo("generates a refresh token");
Expand Down
30 changes: 20 additions & 10 deletions src/targets/initiateAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
UnsupportedError,
} from "../errors";
import { Services } from "../services";
import PrivateKey from "../keys/cognitoLocal.private.json";
import * as uuid from "uuid";

interface Input {
AuthFlow: "USER_PASSWORD_AUTH" | "CUSTOM_AUTH";
Expand Down Expand Up @@ -66,44 +68,52 @@ export const InitiateAuth = ({
throw new InvalidPasswordError();
}

const eventId = uuid.v4();
const authTime = new Date().getTime();

return {
ChallengeName: "PASSWORD_VERIFIER",
ChallengeParameters: {},
AuthenticationResult: {
AccessToken: jwt.sign(
{
sub: user.Username,
event_id: "439a2a30-ecbc-4788-9ce6-fc6eb9a2d535",
event_id: eventId,
token_use: "access",
scope: "aws.cognito.signin.user.admin",
auth_time: 1585450518,
jti: "b398b959-9f2f-40fa-9832-0a237524e460",
scope: "aws.cognito.signin.user.admin", // TODO: scopes
auth_time: authTime,
jti: uuid.v4(),
client_id: body.ClientId,
username: user.Username,
},
"secret",
PrivateKey.pem,
{
issuer: "http://localhost:9229/user-pool-id",
algorithm: "RS256",
issuer: `http://localhost:9229/${userPoolId}`,
expiresIn: "24h",
keyid: "CognitoLocal",
}
),
IdToken: jwt.sign(
{
sub: user.Username,
email_verified: true,
event_id: "439a2a30-ecbc-4788-9ce6-fc6eb9a2d535",
event_id: eventId,
token_use: "id",
auth_time: 1585450518,
auth_time: authTime,
"cognito:username": user.Username,
email: user.Attributes.filter((x) => x.Name === "email").map(
(x) => x.Value
)[0],
},
"secret",
PrivateKey.pem,
{
issuer: "http://localhost:9229/user-pool-id",
algorithm: "RS256",
// TODO: this needs to match the actual host/port we started the server on
issuer: `http://localhost:9229/${userPoolId}`,
expiresIn: "24h",
audience: body.ClientId,
keyid: "CognitoLocal",
}
),
RefreshToken: "<< TODO >>",
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./lib",
"resolveJsonModule": true,
"strict": true,
"target": "ES2019"
},
Expand Down

0 comments on commit 949d3fc

Please sign in to comment.