koa-jwt2
Version:
JWT authentication middleware.
364 lines (311 loc) • 10.6 kB
JavaScript
const jwt = require("jsonwebtoken");
const assert = require("assert");
const koajwt = require("../lib");
const UnauthorizedError = require("../lib/errors/UnauthorizedError");
const mockContext = require("./context");
describe("failure tests", function() {
let ctx;
beforeEach(() => {
ctx = mockContext();
});
it("should throw if options not sent", function() {
try {
koajwt();
} catch (err) {
assert.ok(err);
assert.equal(err.message, "secret should be set");
}
});
it("should throw if no authorization header and credentials are required", async () => {
try {
await koajwt({ secret: "shhhh", credentialsRequired: true })(
ctx,
() => {}
);
} catch (err) {
assert.ok(err);
assert.equal(err.code, "credentials_required");
}
});
it("support unless skip", async () => {
ctx.req.url = "/index.html";
await koajwt({ secret: "shhhh" }).unless({
path: "/index.html",
useOriginalUrl: false
})(ctx, () => {});
assert.ok(true);
});
it("should skip on CORS preflight", async () => {
ctx.req.method = "OPTIONS";
ctx.req.headers = {
"access-control-request-headers": "sasa, sras, authorization"
};
await koajwt({ secret: "shhhh" })(ctx, () => {});
assert.ok(true);
});
it("should throw if authorization header is malformed", async () => {
ctx.headers.authorization = "wrong";
try {
await koajwt({ secret: "shhhh" })(ctx, () => {});
} catch (err) {
assert.ok(err);
assert.equal(err.code, "credentials_bad_format");
}
});
it("should throw if authorization header is not Bearer", async () => {
ctx.headers.authorization = "Basic foobar";
try {
await koajwt({ secret: "shhhh" })(ctx, () => {});
} catch (err) {
assert.equal(err.code, "credentials_bad_scheme");
}
});
it("should next if authorization header is not Bearer and credentialsRequired is false", async () => {
ctx.headers.authorization = "Basic foobar";
await koajwt({ secret: "shhhh", credentialsRequired: false })(
ctx,
() => {}
);
assert.ok(true);
});
it("should throw if authorization header is not well-formatted jwt", async () => {
ctx.headers.authorization = "Bearer wrongjwt";
try {
await koajwt({ secret: "shhhh" })(ctx, () => {});
} catch (err) {
assert.equal(err.code, "invalid_token");
}
});
it("should throw if jwt is an invalid json", async () => {
ctx.headers.authorization =
"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.yJ1c2VybmFtZSI6InNhZ3VpYXIiLCJpYXQiOjE0NzEwMTg2MzUsImV4cCI6MTQ3MzYxMDYzNX0.foo";
try {
await koajwt({ secret: "shhhh" })(ctx, () => {});
} catch (err) {
assert.equal(err.code, "invalid_token");
}
});
it("should throw if authorization header is not valid jwt", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "different-shhhh" })(ctx, () => {});
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "invalid signature");
}
});
it("should throw if audience is not expected", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar", aud: "expected-audience" }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "shhhhhh", audience: "not-expected-audience" })(
ctx,
() => {}
);
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(
err.message,
"jwt audience invalid. expected: not-expected-audience"
);
}
});
it("should throw if token is expired", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "shhhhhh" })(ctx, () => {});
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.inner.name, "TokenExpiredError");
assert.equal(err.message, "jwt expired");
}
});
it("should throw if token issuer is wrong", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar", iss: "http://foo" }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "shhhhhh", issuer: "http://wrong" })(
ctx,
() => {}
);
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "jwt issuer invalid. expected: http://wrong");
}
});
it("should use errors thrown from custom getToken function", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
function getTokenThatThrowsError() {
throw new UnauthorizedError("invalid_token", {
message: "Invalid token!"
});
}
try {
await koajwt({
secret: "shhhhhh",
getToken: getTokenThatThrowsError
})(ctx, () => {});
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "Invalid token!");
}
});
it("should throw error when signature is wrong", async () => {
const secret = "shhh";
const token = jwt.sign({ foo: "bar", iss: "http://www" }, secret);
// manipulate the token
const newContent = new Buffer("{foo: 'bar', edg: 'ar'}").toString("base64");
const splitetToken = token.split(".");
splitetToken[1] = newContent;
const newToken = splitetToken.join(".");
// build request
ctx.headers = [];
ctx.headers.authorization = "Bearer " + newToken;
try {
await koajwt({ secret: secret })(ctx, () => {});
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "invalid token");
}
});
it("should throw error if token is expired even with when credentials are not required", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: secret, credentialsRequired: false })(
ctx,
() => {}
);
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "jwt expired");
}
});
it("should throw error if token is invalid even with when credentials are not required", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "not the secret", credentialsRequired: false })(
ctx,
() => {}
);
} catch (err) {
assert.ok(err);
assert.equal(err.code, "invalid_token");
assert.equal(err.message, "invalid signature");
}
});
});
describe("work tests", function() {
let ctx;
beforeEach(() => {
ctx = mockContext();
});
it("should work if authorization header is valid jwt", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
await koajwt({ secret })(ctx, () => {});
assert.equal("bar", ctx.state.user.foo);
});
it("should work with nested properties", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
await koajwt({ secret, property: "auth.token" })(ctx, () => {});
assert.equal("bar", ctx.state.auth.token.foo);
});
it("should work if authorization header is valid with a buffer secret", async () => {
const secret = new Buffer(
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"base64"
);
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
await koajwt({ secret })(ctx, () => {});
assert.equal("bar", ctx.state.user.foo);
});
it("should set property if option provided", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
await koajwt({ secret, property: "auth" })(ctx, () => {});
assert.equal("bar", ctx.state.auth.foo);
});
it("should work if no authorization header and credentials are not required", async () => {
await koajwt({ secret: "shhhh", credentialsRequired: false })(
ctx,
() => {}
);
assert.ok(true);
});
it("should not work if no authorization header", async () => {
try {
await koajwt({ secret: "shhhh" })(ctx, () => {});
} catch (err) {
assert(typeof err !== "undefined");
}
});
it("should produce a stack trace that includes the failure reason", async () => {
const token = jwt.sign({ foo: "bar" }, "secretA");
ctx.headers.authorization = "Bearer " + token;
try {
await koajwt({ secret: "secretB" })(ctx, () => {});
} catch (err) {
const index = err.stack.indexOf("UnauthorizedError: invalid signature");
assert.equal(
index,
0,
"Stack trace didn't include 'invalid signature' message."
);
}
});
it("should work with a custom getToken function", async () => {
const secret = "shhhhhh";
const token = jwt.sign({ foo: "bar" }, secret);
ctx.query = {
token
};
function getTokenFromQuery(ctx) {
return ctx.query.token;
}
await koajwt({
secret,
getToken: getTokenFromQuery
})(ctx, () => {
assert.equal("bar", ctx.state.user.foo);
});
});
it("should work with a secret async function that accepts header argument", async () => {
const secret = "shhhhhh";
const secretAsync = async function(ctx, headers, payload, cb) {
assert.equal(headers.alg, "HS256");
assert.equal(payload.foo, "bar");
return new Promise(resolve => {
process.nextTick(function() {
resolve(secret);
});
});
};
const token = jwt.sign({ foo: "bar" }, secret);
ctx.headers.authorization = "Bearer " + token;
await koajwt({ secret: secretAsync })(ctx, () => {});
assert.equal("bar", ctx.state.user.foo);
});
});