※ これはあくまでも実験です。本番環境での利用はお勧めしません。
mBaaSでは認証機能としてID/パスワード認証の他、メールアドレス、ソーシャル(Google/Facebook/Twitter)認証、匿名認証を提供しています。しかし運用上、他の認証(今回はGitHub)にも対応したいと思うこともあるでしょう。今回はそんなチャレンジです。
利用するのは匿名認証
GitHubの認証情報に沿って匿名認証用のIDを生成できれば認証は可能です。なお、匿名認証のIDは下記のようなフォーマットになっています。16進数に適合する0〜9の数字とa-fの文字列が8桁/4桁/4桁/4桁/4桁/12桁とつながって、全体で32桁(と5つの-)となっています。正規表現で表すと次のようになります。
[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
GitHubの認証情報から、このIDを生成できればNCMBでも認証が可能になります。
GitHub認証を行う
GitHubで認証を行うには、まずGitHubアプリを作ります。作るとクライアントIDとクライアントシークレットの二つの情報が手に入ります。
まずクライアントIDを使って、GitHubで認証に関する許可を得ます。その際、ランダムな文字列を渡して(下記state)、レスポンスでそのstateを確認することで、改善されていないことを保証します。
const gitHubAuthUrl = 'https://github.com/login/oauth/authorize'; const clientId = 'Iv1...ea6'; const redirectUri = 'http://localhost:8000/'; const state = Math.random().toString(32).substring(2); location.href = `${gitHubAuthUrl}?redirect_uri=${redirectUri}&client_id=${clientId}&state=${state}&scope=user`;
コードを取得する
GitHubでアクセスを許可すると認可コードが得られます。これを使ってアクセストークンを得るのですが、次のステップではクライアントシークレットを使います。クライアントシークレットをWebブラウザやクライアントサイドのJavaScriptに記述してしまうのは避けたいので、次の処理はスクリプト機能を使います。クエリストリングからの取得は js-url を使っています。
const code = url('?code'); const state = url('?state'); await ncmb.Script .query({ code: code, state: state }) .exec("GET", "githtub.js")
アクセストークンを取得する
ここからはスクリプトの処理です。まず先ほど受け取ったコードを使ってアクセストークンを取得します。
const request = require('superagent'); const clientId = 'Iv1...ea6'; const clientSecret = '160...cd1'; const redirectUri = 'http://localhost:8000/'; const response = await request .post('https://github.com/login/oauth/access_token') .set('Accept', 'application/json') .send({ client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, code: req.query.code, state: req.query.state }); const json = response.body; if (!json.access_token) { return res.send(json); }
ユーザ情報を取得する
アクセストークンが得られたら、そのトークンを使ってログインユーザの情報を取得します。この時のTipsとして、ユーザエージェントが必須なので注意してください。アクセストークンはAuthorizationヘッダーに token という文字を頭につけて記述します。
const userResponse = await request .get('https://api.github.com/user') .set({ 'Accept': 'application/json', 'User-Agent': 'curl/7.43.0', 'Authorization': `token ${json.access_token}`, }) .send(); const user = userResponse.body;
これでユーザ情報が手に入りました。
匿名認証用のIDを生成する
問題はここからで、ユーザ情報から匿名認証用のIDを生成する処理が必要です。そこで注目したのがnode_idです。これはGraphQL用のIDとのことで、次のような文字列になります。
MDU6SXNzdWU0MzIzNTg0NzE=
この文字列を下記のように変換すると、32桁の16進数になります。具体的な処理はコードを参照してください。
const code = user.node_id.split("").map(a => a.charCodeAt(0).toString(16)).join(""); // 4d44553653584e7a645755304d7a497a4e5467304e7a453d
32桁の16進数になれば、後は正規表現で桁数に区切るだけです。
const id = num.replace(/([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})/, '$1-$2-$3-$4-$5'); // 4d445536-5358-4e7a-6457-55304d7a497a4e5467304e7a453d
匿名認証を行う
IDが生成できたら、匿名認証を実行します。認証したら、ユーザ情報を返します。
const auth = await ncmb.User.loginAsAnonymous(id);
res.send(ncmb.User.getCurrentUser());
これでGitHubを使った認証処理が完了しました。
まとめ
node_idが不変なものであるか、常に32桁の16進数になるとは限らないので注意してください。とはいえ、IDさえ何らかの条件に従って生成できれば、別な認証プロバイダを組み合わせるのは難しくなさそうです。もちろん、その生成アルゴリズムが漏洩すると、直接認証されてしまう可能性もあるので注意してください。少なくとも生成はサーバサイドで行うべきでしょう。
今回のコードはあくまでも実験ですが、何かの参考になれば幸いです。