自前で JWT を発行、検証を Node.js で行う

Node.js で JSON Web Token (JWT) の ID トークンを発行し、検証する処理のメモです。 JWT のメリットはアクセストークンそのものが有効期間やコンテクストを保持できるため、個別にリクエストごとにセッションIDからセッション情報を引くという機構を省くことができます。そのため、自前の API サーバにおいて、アクセストークンとして JWT の ID トークンを使えるとサーバ処理を省力化できます。 今回の例では、JWT の検証をグローバルにできるようにはせず、サーバ内でのみ行うこととします。一般的な JWT トークンは JSON Web Key (JWK) Set から提供されており、JWT の署名検証ができるようになっていますが、それは行いません。 依存ライブラリ Auth0 がものすごく便利なライブラリを提供してくれています。 https://github.com/auth0/node-jsonwebtoken $ npm i -save jsonwebtoken $ npm i -save-dev @types/jsonwebtoken 鍵の生成 公開鍵と秘密鍵を別々にすることも可能ですが、分けると管理が面倒なのと同一サーバ内で処理するため、PEMファイルで管理します。 $ openssl genrsa > auth.pem 発行と検証処理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import fs from 'fs' import jwt from 'jsonwebtoken' const PEM = fs.readFileSync('auth.pem') function issueToken(subject: string): string { const opts: jwt.SignOptions = { algorithm: 'RS256', issuer: 'https://issuer.example.com', expiresIn: '1d', // https://github.com/zeit/ms } const payload = { sub: subject, } return jwt.sign(payload, PEM, opts) } export async function verifyToken(idToken: string): Promise<object> { const opts: jwt.VerifyOptions = { algorithms: ['RS256'], issuer: 'https://issuer.example.com', maxAge: '1d', // https://github.com/zeit/ms } return new Promise((resolve, reject) => { jwt.verify(idToken, PEM, opts, (err: Error, decoded: any) => { if (err) { reject(err) } else { resolve(decoded) } }) }) } // 実行確認 ;(async () => { const idToken = issueToken('userId') const result = await verifyToken(idToken) console.log(result) })() JWT 署名時の expiresIn より時間が経過している場合は、検証時に TokenExpiredError: jwt expired がスローされます。 時間内であっても発行時よりも maxAge が経過している場合は、検証時に TokenExpiredError: maxAge exceeded がスローされます。 実行結果 1 2 3 4 5 6 { sub: 'userId', iat: 1582639938, exp: 1582726338, iss: 'https://issuer.example.com' } まとめ ログイン成功時に issueToken を呼んで ID トークンをクライアントに発行します。クライアントは API リクエストの Authorization ヘッダに付与して、サーバ側は verifyToken で検証することで、認証および subject(sub)フィールドなどからユーザー情報を取得することができます。

2020年2月24日 · Toshimitsu Takahashi

Node.js アプリに TypeScript を導入する

既存の Node.js アプリケーションを徐々に TypeScript 化していく場合の導入方法のメモです。 Migrating from JavaScript | TypeScript オフィシャルサイトのこちらのぺージが参考になりますが、クライアントサイドを例書かれているので、サーバーサイドは勝手が違います。 typescript と ts-node のインストール まず typescript と ts-node しましょう。 ts-node はコンパイルせずに直接 ts ファイルを実行できます。 $ npm install -g typescript ts-node または -g ではなく --save-dev としてローカルにインストールします。 tsconfig.json の生成 $ tsc --init message TS6071: Successfully created a tsconfig.json file. tsconfig.json 生成されるので、 "target": "es2018", allowJs: true, "outDir": "./dist" にそれぞれ変更します。理由は Promise などを使えるようにするため、JavaScriptファイルはそのまま使うため、コンパイルしたものを dist ディレクトリに出力するためです。 tsconfig.json 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 { "compilerOptions": { /* Basic Options */ "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": [], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./dist", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "incremental": true, /* Enable incremental compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ // "noEmit": true, /* Do not emit outputs. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ // "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ } } main ファイルを .ts にする 例えばメインファイルの app.js を app.ts にリネームして、 tsc で実行します。 ...

2019年4月19日 · Toshimitsu Takahashi

Node.js Koa の RESTful API サーバに GraphQL を導入する

Node.js の HTTP サーバフレームワーク Koa で作られた API サーバに GraphQL 導入する方法です。 簡略化した例として、TodoリストのAPIサーバとして POST /api/todo で新規アイテムを登録し、 GET /api/todo でリストで取得できるもので説明します。同様の操作ができる機能を GraphQL で提供します。 RESTful API サーバの実装状態 koa-router はネストすることが可能なので、 /api は apiRouter として定義し、後で /graphql は別途 graphqlRouter として利用します。 package.json 依存する npm モジュールは以下の通りです。 1 2 3 4 5 6 7 8 9 { "dependencies": { "graphql": "^14.2.1", "koa": "^2.7.0", "koa-bodyparser": "^4.2.1", "koa-graphql": "^0.8.0", "koa-router": "^7.4.0" } } models/todo.js メモリ上の配列でTodoを管理する簡易的なものです。中身は同期処理ですが、より実践的にするため async (Promise) にしています。 ...

2019年4月12日 · Toshimitsu Takahashi

Mongoose で完全なる ES6 Promise を使う

現行の Mongoose 4.2.4 ではネイティブ Promise がサポートされていない。Query オブジェクトの exec メソッドを引数無しで呼び出すと、Mongoose 独自の Promise が返るがチェインの中で利用すると上手く動かなかった。 [http://mongoosejs.com/docs/api.html#promise_Promise] リンク先に Mongoose 5.0 でネイティブ Promise がサポートされるとあるが今使いたい。そこで Query オブジェクトに execPromise メソッドを拡張実装してみる。 1 2 3 4 5 6 7 8 9 10 11 var mongoose = require('mongoose'); mongoose.Query.prototype.execPromise = function() { var self = this; return new Promise(function(resolve, reject){ self.exec(function(err, data){ if (err) reject(err); else resolve(data); }); }); } 以下のようにチェインで使うことができる。 ...

2015年11月9日 · Toshimitsu Takahashi

GitHub に公開した Node.js スクリプトに Travis CI を導入し Coveralls でカバレッジ管理するまで

GitHub に httpreqtest という Node.js で動くクライアントのHTTPリクエスト内容を JSON でそのまま返すスクリプトを公開した。非公開のトリッキーな仕様の API サービスに対して、クライアントを作るにあたり、実装チェックする必要があり勢いで作った。 このリポジトリ tilfin/httpreqtest · GitHub に build pass と coverage バッジを付けたいと思い、Travis CI を導入しテストを走らせ、Coveralls でコードのカバレッジが表示できるようにしたので、それをメモしておく。 テスト まずテストがなければ始まらないので、Mocha と SuperTest を使ってテストを書いた。 mocha と supertest を npm でインストールし、package.json の devDependencies に保存する。 $ npm install mocha –save-dev test ディレクトリを掘り、その中にテストコードを書いた jsファイルを置く。 package.json の scripts には { “test”: “mocha” } と記述して、npm test で実行されるようになる。 Travis CI ブラウザから Travis CI に GitHub アカウントでサインインする。リポジトリ一覧から当該のものをオンにする。これだけ諸々の設定が済みます。 あと、Git リポジトリのルートに .travis.yml を置きます。 .travis.yml language: node_js node_js: - "0.10" 上記をコミットしてプッシュして、Travis CI をブラウザで開いていると、ビルドおよびテストが実行されます。正常に終了すれば念願の build pass バッジになります。これを README.md に追加しておきましょう。 ...

2013年12月3日 · Toshimitsu Takahashi

Mongoose でセカンダリにアクセス可能な接続を設定をするには

MongoDB でレプリケーションセットを構成する。MongoDBでは Secondary サーバへのアクセスを読み取りであってもデフォルトで拒否するようになっている。 このためには接続ごとに slaveOk フラグを立てる必要があるが、そのときの Mongoose の設定に手間取ったのでメモしておく。 (2013/1/18 に、レプリケーション設定に誤りがあったため以降の内容を修正しています。) オプション設定 mongoose の connection を生成する方法は色々とありますが、createConnection メソッドを使っています。 createConnection の第2引数のオプションには、それぞれ以下のキーで設定を構成できます。 db - Node用 Native MongoDB ドライバーのオプション server - サーバ接続に関する設定 replset - レプリケーションセットに関する設定 ReplSet — MongoDB Node.JS Driver 1.3.20 documentation 接続を生成する関数例 以下、sampledb というデータベースに localhost で 27017 と 27018 ポートで動いている二つの MongoDB サーバに接続するときのサンプルです。レプリケーションセット名は「myrepl」で、コネクションプールもデフォルトの 5 から 10 に増やしています。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const DB_HOST = "localhost:27017"; const DB_NAME = "sampledb"; const DB_REPLSET_NAME = "myrepl"; const DB_REPLSET_MEMBERS = "localhost:27017,localhost:27018" var mongoose = require('mongoose'); var mongooseConnection = (function(){ var uri; var opts = { server: { poolSize: 10, auto_reconnect: true } }; if (DB_REPLSET_NAME && DB_REPLSET_MEMBERS) { var rsmembers = DB_REPLSET_MEMBERS.split(','); var svs = []; rsmembers.forEach(function(sv){ svs.push("mongodb://" + sv + "/" + DB_NAME); }); uri = svs.join(","); opts.replset = { rs_name: DB_REPLSET_NAME, readPreference: 'primaryPreferred' }; } else { opts.db = { slave_ok: true }; uri = 'mongodb://' + DB_HOST + '/' + DB_NAME; } return mongoose.createConnection(uri, opts); })(); ※ DB_REPLSET_NAME, DB_REPLSET_MEMBERS が設定されていればレプリケーション設定になります。

2013年1月14日 · Toshimitsu Takahashi

Node.js に npm で express をインストールして試す

node.js と npm を Ubuntu にセットアップ - Tosshi Note の続きです。 npm で Express - node.js web application framework を入れて動かす。 express は node.js をさらに使いやすくするフレームワーク。使ったことがないが Ruby の Sinatra ライクだそうです。 express をインストール g はグローバルオプション。これを付けないとコマンド実行パスの node_modules にインストールされる。 $ sudo npm install -g express expressのヘルプ確認 セッション機能やテンプレート、CSSエンジンの指定ができることがわかる。 $ express -h Usage: express [options] [path] Options: -s, –sessions add session support -t, –template add template support (jade|ejs). default=jade -c, –css add stylesheet support (less|sass|stylus). default=plain css -v, –version output framework version -h, –help output help information ...

2011年9月26日 · Toshimitsu Takahashi

Node.js と npm を Ubuntu にセットアップ

Ubuntu を 11.04 に上げたが、apt-get で入る node.js が 0.2.x だったので、ソースからコンパイルしてインストールすることにした。 当初 github からチェックアウトして 0.5.x で試していたが、最終的に express が未対応だったので、安定板をダウンロードして入れることにした。 さらに node.js のパッケージ管理ツール npm もインストールする。 node.js インストール node.js をインストール。 必要パッケージの準備 コンパイル、ビルド、依存ライブラリのインストール $ sudo apt-get update $ sudo apt-get install gcc $ sudo apt-get install libssl-dev $ sudo apt-get install build-essential opensslをコンパイル時に参照できるようにパッケージを設定 $ sudo pkg-config openssl –cflags –libs -lssl -lcrypto 最近のバージョンでは $ sudo apt-get install libcurl4-openssl-dev を行うとよいようだ(2012/3/30 追記)。 ダウンロードからビルド・インストール http://nodejs.org/#download から安定板の 0.4.12 を落としてビルドインストール。 $ wget http://nodejs.org/dist/node-v0.4.12.tar.gz $ tar zxvf node-v0.4.12.tar.gz $ cd node-v0.4.12 $ ./configure $ make $ sudo make install ...

2011年9月23日 · Toshimitsu Takahashi