jwt.test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. const jwt = require("jsonwebtoken");
  2. const assert = require("assert");
  3. const koajwt = require("../lib");
  4. const UnauthorizedError = require("../lib/errors/UnauthorizedError");
  5. const mockContext = require("./context");
  6. describe("failure tests", function() {
  7. let ctx;
  8. beforeEach(() => {
  9. ctx = mockContext();
  10. });
  11. it("should throw if options not sent", function() {
  12. try {
  13. koajwt();
  14. } catch (err) {
  15. assert.ok(err);
  16. assert.equal(err.message, "secret should be set");
  17. }
  18. });
  19. it("should throw if no authorization header and credentials are required", async () => {
  20. try {
  21. await koajwt({ secret: "shhhh", credentialsRequired: true })(
  22. ctx,
  23. () => {}
  24. );
  25. } catch (err) {
  26. assert.ok(err);
  27. assert.equal(err.code, "credentials_required");
  28. }
  29. });
  30. it("support unless skip", async () => {
  31. ctx.req.url = "/index.html";
  32. await koajwt({ secret: "shhhh" }).unless({
  33. path: "/index.html",
  34. useOriginalUrl: false
  35. })(ctx, () => {});
  36. assert.ok(true);
  37. });
  38. it("should skip on CORS preflight", async () => {
  39. ctx.req.method = "OPTIONS";
  40. ctx.req.headers = {
  41. "access-control-request-headers": "sasa, sras, authorization"
  42. };
  43. await koajwt({ secret: "shhhh" })(ctx, () => {});
  44. assert.ok(true);
  45. });
  46. it("should throw if authorization header is malformed", async () => {
  47. ctx.headers.authorization = "wrong";
  48. try {
  49. await koajwt({ secret: "shhhh" })(ctx, () => {});
  50. } catch (err) {
  51. assert.ok(err);
  52. assert.equal(err.code, "credentials_bad_format");
  53. }
  54. });
  55. it("should throw if authorization header is not Bearer", async () => {
  56. ctx.headers.authorization = "Basic foobar";
  57. try {
  58. await koajwt({ secret: "shhhh" })(ctx, () => {});
  59. } catch (err) {
  60. assert.equal(err.code, "credentials_bad_scheme");
  61. }
  62. });
  63. it("should next if authorization header is not Bearer and credentialsRequired is false", async () => {
  64. ctx.headers.authorization = "Basic foobar";
  65. await koajwt({ secret: "shhhh", credentialsRequired: false })(
  66. ctx,
  67. () => {}
  68. );
  69. assert.ok(true);
  70. });
  71. it("should throw if authorization header is not well-formatted jwt", async () => {
  72. ctx.headers.authorization = "Bearer wrongjwt";
  73. try {
  74. await koajwt({ secret: "shhhh" })(ctx, () => {});
  75. } catch (err) {
  76. assert.equal(err.code, "invalid_token");
  77. }
  78. });
  79. it("should throw if jwt is an invalid json", async () => {
  80. ctx.headers.authorization =
  81. "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.yJ1c2VybmFtZSI6InNhZ3VpYXIiLCJpYXQiOjE0NzEwMTg2MzUsImV4cCI6MTQ3MzYxMDYzNX0.foo";
  82. try {
  83. await koajwt({ secret: "shhhh" })(ctx, () => {});
  84. } catch (err) {
  85. assert.equal(err.code, "invalid_token");
  86. }
  87. });
  88. it("should throw if authorization header is not valid jwt", async () => {
  89. const secret = "shhhhhh";
  90. const token = jwt.sign({ foo: "bar" }, secret);
  91. ctx.headers.authorization = "Bearer " + token;
  92. try {
  93. await koajwt({ secret: "different-shhhh" })(ctx, () => {});
  94. } catch (err) {
  95. assert.ok(err);
  96. assert.equal(err.code, "invalid_token");
  97. assert.equal(err.message, "invalid signature");
  98. }
  99. });
  100. it("should throw if audience is not expected", async () => {
  101. const secret = "shhhhhh";
  102. const token = jwt.sign({ foo: "bar", aud: "expected-audience" }, secret);
  103. ctx.headers.authorization = "Bearer " + token;
  104. try {
  105. await koajwt({ secret: "shhhhhh", audience: "not-expected-audience" })(
  106. ctx,
  107. () => {}
  108. );
  109. } catch (err) {
  110. assert.ok(err);
  111. assert.equal(err.code, "invalid_token");
  112. assert.equal(
  113. err.message,
  114. "jwt audience invalid. expected: not-expected-audience"
  115. );
  116. }
  117. });
  118. it("should throw if token is expired", async () => {
  119. const secret = "shhhhhh";
  120. const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
  121. ctx.headers.authorization = "Bearer " + token;
  122. try {
  123. await koajwt({ secret: "shhhhhh" })(ctx, () => {});
  124. } catch (err) {
  125. assert.ok(err);
  126. assert.equal(err.code, "invalid_token");
  127. assert.equal(err.inner.name, "TokenExpiredError");
  128. assert.equal(err.message, "jwt expired");
  129. }
  130. });
  131. it("should throw if token issuer is wrong", async () => {
  132. const secret = "shhhhhh";
  133. const token = jwt.sign({ foo: "bar", iss: "http://foo" }, secret);
  134. ctx.headers.authorization = "Bearer " + token;
  135. try {
  136. await koajwt({ secret: "shhhhhh", issuer: "http://wrong" })(
  137. ctx,
  138. () => {}
  139. );
  140. } catch (err) {
  141. assert.ok(err);
  142. assert.equal(err.code, "invalid_token");
  143. assert.equal(err.message, "jwt issuer invalid. expected: http://wrong");
  144. }
  145. });
  146. it("should use errors thrown from custom getToken function", async () => {
  147. const secret = "shhhhhh";
  148. const token = jwt.sign({ foo: "bar" }, secret);
  149. function getTokenThatThrowsError() {
  150. throw new UnauthorizedError("invalid_token", {
  151. message: "Invalid token!"
  152. });
  153. }
  154. try {
  155. await koajwt({
  156. secret: "shhhhhh",
  157. getToken: getTokenThatThrowsError
  158. })(ctx, () => {});
  159. } catch (err) {
  160. assert.ok(err);
  161. assert.equal(err.code, "invalid_token");
  162. assert.equal(err.message, "Invalid token!");
  163. }
  164. });
  165. it("should throw error when signature is wrong", async () => {
  166. const secret = "shhh";
  167. const token = jwt.sign({ foo: "bar", iss: "http://www" }, secret);
  168. // manipulate the token
  169. const newContent = new Buffer("{foo: 'bar', edg: 'ar'}").toString("base64");
  170. const splitetToken = token.split(".");
  171. splitetToken[1] = newContent;
  172. const newToken = splitetToken.join(".");
  173. // build request
  174. ctx.headers = [];
  175. ctx.headers.authorization = "Bearer " + newToken;
  176. try {
  177. await koajwt({ secret: secret })(ctx, () => {});
  178. } catch (err) {
  179. assert.ok(err);
  180. assert.equal(err.code, "invalid_token");
  181. assert.equal(err.message, "invalid token");
  182. }
  183. });
  184. it("should throw error if token is expired even with when credentials are not required", async () => {
  185. const secret = "shhhhhh";
  186. const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
  187. ctx.headers.authorization = "Bearer " + token;
  188. try {
  189. await koajwt({ secret: secret, credentialsRequired: false })(
  190. ctx,
  191. () => {}
  192. );
  193. } catch (err) {
  194. assert.ok(err);
  195. assert.equal(err.code, "invalid_token");
  196. assert.equal(err.message, "jwt expired");
  197. }
  198. });
  199. it("should throw error if token is invalid even with when credentials are not required", async () => {
  200. const secret = "shhhhhh";
  201. const token = jwt.sign({ foo: "bar", exp: 1382412921 }, secret);
  202. ctx.headers.authorization = "Bearer " + token;
  203. try {
  204. await koajwt({ secret: "not the secret", credentialsRequired: false })(
  205. ctx,
  206. () => {}
  207. );
  208. } catch (err) {
  209. assert.ok(err);
  210. assert.equal(err.code, "invalid_token");
  211. assert.equal(err.message, "invalid signature");
  212. }
  213. });
  214. });
  215. describe("work tests", function() {
  216. let ctx;
  217. beforeEach(() => {
  218. ctx = mockContext();
  219. });
  220. it("should work if authorization header is valid jwt", async () => {
  221. const secret = "shhhhhh";
  222. const token = jwt.sign({ foo: "bar" }, secret);
  223. ctx.headers.authorization = "Bearer " + token;
  224. await koajwt({ secret })(ctx, () => {});
  225. assert.equal("bar", ctx.state.user.foo);
  226. });
  227. it("should work with nested properties", async () => {
  228. const secret = "shhhhhh";
  229. const token = jwt.sign({ foo: "bar" }, secret);
  230. ctx.headers.authorization = "Bearer " + token;
  231. await koajwt({ secret, property: "auth.token" })(ctx, () => {});
  232. assert.equal("bar", ctx.state.auth.token.foo);
  233. });
  234. it("should work if authorization header is valid with a buffer secret", async () => {
  235. const secret = new Buffer(
  236. "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
  237. "base64"
  238. );
  239. const token = jwt.sign({ foo: "bar" }, secret);
  240. ctx.headers.authorization = "Bearer " + token;
  241. await koajwt({ secret })(ctx, () => {});
  242. assert.equal("bar", ctx.state.user.foo);
  243. });
  244. it("should set property if option provided", async () => {
  245. const secret = "shhhhhh";
  246. const token = jwt.sign({ foo: "bar" }, secret);
  247. ctx.headers.authorization = "Bearer " + token;
  248. await koajwt({ secret, property: "auth" })(ctx, () => {});
  249. assert.equal("bar", ctx.state.auth.foo);
  250. });
  251. it("should work if no authorization header and credentials are not required", async () => {
  252. await koajwt({ secret: "shhhh", credentialsRequired: false })(
  253. ctx,
  254. () => {}
  255. );
  256. assert.ok(true);
  257. });
  258. it("should not work if no authorization header", async () => {
  259. try {
  260. await koajwt({ secret: "shhhh" })(ctx, () => {});
  261. } catch (err) {
  262. assert(typeof err !== "undefined");
  263. }
  264. });
  265. it("should produce a stack trace that includes the failure reason", async () => {
  266. const token = jwt.sign({ foo: "bar" }, "secretA");
  267. ctx.headers.authorization = "Bearer " + token;
  268. try {
  269. await koajwt({ secret: "secretB" })(ctx, () => {});
  270. } catch (err) {
  271. const index = err.stack.indexOf("UnauthorizedError: invalid signature");
  272. assert.equal(
  273. index,
  274. 0,
  275. "Stack trace didn't include 'invalid signature' message."
  276. );
  277. }
  278. });
  279. it("should work with a custom getToken function", async () => {
  280. const secret = "shhhhhh";
  281. const token = jwt.sign({ foo: "bar" }, secret);
  282. ctx.query = {
  283. token
  284. };
  285. function getTokenFromQuery(ctx) {
  286. return ctx.query.token;
  287. }
  288. await koajwt({
  289. secret,
  290. getToken: getTokenFromQuery
  291. })(ctx, () => {
  292. assert.equal("bar", ctx.state.user.foo);
  293. });
  294. });
  295. it("should work with a secret async function that accepts header argument", async () => {
  296. const secret = "shhhhhh";
  297. const secretAsync = async function(ctx, headers, payload, cb) {
  298. assert.equal(headers.alg, "HS256");
  299. assert.equal(payload.foo, "bar");
  300. return new Promise(resolve => {
  301. process.nextTick(function() {
  302. resolve(secret);
  303. });
  304. });
  305. };
  306. const token = jwt.sign({ foo: "bar" }, secret);
  307. ctx.headers.authorization = "Bearer " + token;
  308. await koajwt({ secret: secretAsync })(ctx, () => {});
  309. assert.equal("bar", ctx.state.user.foo);
  310. });
  311. });