Cognitoの認証にSlackのユーザ情報を使おうと思ったら、SlackではOpenID Connectをサポートしていないからできないとのことだったので、無理矢理OpenID Connectに対応させる方法を探してみました。
前回の記事で試したSlack Oauth2.0を利用して実装しました。
SlackのOAuth2.0のみを使いたいという方はこちらを参照ください。
CognitoとSlack連携の上での問題点
Amazon CognitoにはOpenID Connectによる連携がサポートされています(Open ID Connect プロバイダー (ID プール) - Amazon Cognito)。
これを使って、LINEやYahooを連携させている方もいます。(AWS CognitoにGoogleとYahooとLINEアカウントを連携させる - Qiita)
この方法であれば、キーを取得してCognitoに設定する程度で連携することができます。
しかし、SlackはOAuth2.0によるAPIの利用はできるのですが、OpenID Connectには対応していないので、Cognitoと直接連携することはできないです。非常に残念
そこで、自身でCognitoとSlackを中継するようなサーバを用意して無理矢理連携させてみました。
イメージ
必要な処理を端折っている部分はありますが、以下のようなフローで進むイメージです。
OpenID Connect対応のサーバ(OpenID Provider)を開発して、実際の認証周りはSlackのOAuth2.0APIに丸投げしている感じです。
環境
- Node.js v10.15.3
- express 4.17
- request 2.88
前回の記事で作成したプログラムを利用してOpenID Providerを作っていきます。
また、OpenID ProviderはCognitoから見えるようパブリックに公開させる必要があります。
最終的にはLambdaでやりたいのですが、手っ取り早く動作確認したかったので ngrok を使いました。
初めて使ったのですが、 ngrokが便利すぎる - Qiita で紹介されているように本当に便利すぎます。
ただ、セキュリティリスクの話はあるので今回は自宅でお試ししてみました。
ですので、ところどころ前回とアイコン周りが違ったりします。
手順概要
- ngrokでポートを公開
- OpenID Providerサーバを起動
- Slackアプリに登録する
- Slackアプリの権限設定などを行う
- CognitoにOpenID Providerの設定を行う
ngrokでポートを公開
今回はlocalhostに立てたサーバをngrok
を使って公開してみました。初めて使いましたが非常に便利です。
Githubアカウントあればすぐに使えるのも非常に楽でした。
公式ページ見ただけでも特に詰まることもなかったので細かい手順などは書きませんが、起動すると以下のようにローカルサーバを公開してくれます。
ngrokが便利すぎる - Qiita を参考にしていただければ十分わかると思います。
ngrokの準備が整ったら以下のコマンドで3000ポートを公開します。同じのようなログが流れればOKです。
$ ngrok http 3000
ngrok by @inconshreveable
Session Status online
Account dbgso (Plan: Free)
Version 2.3.29
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://XXXX.ngrok.io -> http://localhost:3000
Forwarding https://XXXX.ngrok.io -> http://localhost:3000
Connections ttl opn rt1 rt5 p50 p90
184 0 0.00 0.00 5.50 120.02
https://XXXX.ngrok.io
のXXXX
の部分は起動のたびに変わるので、起動したままにしておいてください。(お金を払えば固定できるみたいです。 ngrokで常に同じアドレスを使う方法 - Qiita)
Cognitoにはhttps必須なので、httpsのほうを使います。
本当は最後に起動が良いのですが、SlackにURLを登録しないといけないので手順上は最初にURLを押さえておきます。
Slackアプリの登録・設定
SlackのOAuth2.0を使う必要があるため、前回の記事の手順の内、Slackアプリの作業を実施しておいてください。
設定は同じで大丈夫です。Redirect URLsのみ、今回のngrokで取得したhttps://XXXX.ngrok.io
にしておいてください。
OpenID Providerサーバを起動
ファイル一つで完結するので、ソース貼っちゃいます。
一記事でやるとややこしそうだったので、動作の詳細については別記事にしようと思います。
※ 今回はSlack向けに必要最低限のフローしかサポートしていないため、OpenIDの仕様をフルでは網羅していません。
依存ライブラリは以下のコマンドで入れておいてください
yarn add express request body-parser # or npm install express request body-parser
以下の、Cognitoとngrokのドメインを実際のものと合わせてください。リージョンとかも合っているか注意
この辺り動的に行けると思うんですが、色々疲れたのでひとまずこれで
- const url = `https://XXXX.auth.us-west-2.amazoncognito.com/oauth2/idpresponse?state=${req.query.state}&code=${code}&scope=openid`
+ const url = `https://<実際のドメイン>.auth.us-west-2.amazoncognito.com/oauth2/idpresponse?state=${req.query.state}&code=${code}&scope=openid`
- const slack_redirect_url = 'https://XXXX##.ngrok.io/slackendpoint'
+ const slack_redirect_url = 'https://<実際のドメイン>.ngrok.io/slackendpoint'
//@ts-check
const request = require('request')
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
// // SSL関連でエラーが出るので、応急処置 SlackのAPIにリクエストするときに死んでいる模様。おそらく社内ネットワークのせい
// process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
const slack_redirect_url = 'https://XXXX##.ngrok.io/slackendpoint'
// Slackの認可コード受け取りのための
app.get('/slackendpoint', (req, res) => {
// 認可コードの取得
const code = req.query["code"];
const url = `https://XXXX.auth.us-west-2.amazoncognito.com/oauth2/idpresponse?state=${req.query.state}&code=${code}&scope=openid`
res.redirect(url);
return
});
// Cognito Authorization endpoint
app.get('/authorization', (req, res) => {
// Cognitoからの
console.log("auth", req)
const redirect_uri = req.query.redirect_uri
const state = req.query.state;
const client_id = req.query.client_id;
const url = `https://slack.com/oauth/authorize?client_id=${client_id}&scope=identify&redirect_uri=${slack_redirect_url}&state=${state}&target_uri=${redirect_uri}`;
res.redirect(url)
});
// Cognito Token endpoint
app.post('/token', (req, res, next) => {
console.log("token", req);
const client_id = req.body.client_id;
const client_secret = req.body.client_secret;
request({
url: "https://slack.com/api/oauth.access",
method: "POST",
form: {
client_id: client_id,
client_secret: client_secret,
code: req.body.code,
redirect_uri: slack_redirect_url
}
}, (error, response, body) => {
res.setHeader('Content-Type', 'application/json');
const param = JSON.parse(body);
const access_token = param['access_token'] // アクセストークン
const j = {
access_token: access_token,
expires_in: 3600,
"token_type": "Bearer",
}
res.send(j)
})
})
// Cognito Userinfo endpoint
app.get('/userinfo', (req, res) => {
res.setHeader('Content-Type', 'application/json');
const access_token = req.headers.authorization.split(' ')[1];
console.log("userinfo", req)
// ユーザIDを取得するためのリクエスト
request("https://slack.com/api/auth.test", {
method: "POST",
form: {
token: access_token
}
}, (error, response, body) => {
const user = JSON.parse(body);
console.log(user);
// アクセストークンを使ってユーザ情報をリクエスト
request("https://slack.com/api/users.info ", {
method: 'POST',
form: {
token: access_token,
user: user.user_id
}
}, (error, response, body) => {
const params = JSON.parse(body).user;
params.sub = params.id
params.email = params.profile.email
console.log(params)
res.send(params)
})
})
})
app.listen(3000, () => {
console.log('HTTP Server(3000) is running.');
});
以下のコマンドで3000ポートで起動します
node app.js
CognitoにOpenID Providerの設定を行う
デフォルト設定で作成した後に変更するのが簡単だったので、それでいきます。Review defaults
でCreate pool
で大丈夫です。
App client
- 適当に名前をつけて、
Generate client secret
のチェックは外す。 - 保存
-
App client id
を控えておく
Domain name
- 被らないように適当にドメインをつける
- ソースコードに書いたドメイン名と合わせること
- 動作確認時はこのURLを使います
- 保存
Identity providers
- OpenID Connectを洗濯
- 以下を設定
- Create provider
設定項目は以下の通りです。
設定項目名 | 設定値 | 備考 |
---|---|---|
Provider name | Slack | Cognitoのログイン画面に表示される名前 |
Client ID | Slackのclient id | 登録したSlackアプリから確認 |
Client secret | Slackのclient secret | 登録したSlackアプリから確認 |
Attributes request method | GET | |
Authorize scope | openid email | opeind必須 |
Issuer | https://slack.com |
今回は手作業で各エンドポイントを設定するため、ダミーでも良い |
Authorization endpoint | https://XXXX.ngrok.io/authorization |
パスは任意。実装と一致すれば良い |
Token endpoint | https://XXXX.ngrok.io/token |
同上 |
Userinfo endpoint | https://XXXX.ngrok.io/userinfo |
同上 |
Jwks uri | https://XXXX.ngrok.io/jwks |
未使用。今回の例の場合はいらない? |
先ほど起動したサーバがAuthorization endpoint
、Token endpoint
、Userinfo endpoint
へのリクエストを受け取るOpenID Providerとして動作します
Attribute mapping
OpenID Providerから返した情報をCognitoに反映するためにAttribute mapping
を設定します。
- OIDC attribute -> Email を追加します
- sub -> Username は必須のようなのでそのままです
- Save changes
App client settings
- Enabled Identify Providers の
Slack
にチェック(Identity providersに追加すると出てきます) - Callback、SignoutのURLを今回は
http://localhost:8000
にする(実際のアプリに合わせる)- ここで入れるURLはOpenID Providerではなく、サービス側のアプリです。
- 今回はIDを受け取るだけなので、Pythonの簡易サーバにします
- Authorization code grantを指定(Slackはこれのみ対応のため)
- Allows OAuth Scopesに
openid
を指定(必須のため) - Save changes
動作確認
確認用のアプリとして、pythonのhttp.server
を立てておきます
$ python3 -m http.server -p 8000
以下のURLにアクセス。アドレスなど間違えないように
https://<cognitoのドメイン>/login?response_type=code&client_id=<cognitoのclient id>&redirect_uri=http://localhost:8000
こんな画面がでるはずです
Slack
ボタンを押すと、Slackの認可ページに飛びます
Authorize
を押すとOpenID Provider
、Slack
、Cognito
の間でやりとりが行われて、アプリ(http.server)へcodeと共にリダイレクトされます。(すみません、キャプチャでは8082ポートになっていますね)
CognitoのUser Poolを確認するとSlack
で始まるユーザが追加されています。
これで、SlackとCognitoを連携させることができました
おわりに
Cognito+Slackユーザとしてログインさせる処理をさせたかったので調べてみました。
OpenID Connectの知識ほぼ0の状態でいきなり特殊なことをしたので、疲れました。。。
ただ、OpenIDの通信の流れはよく理解できた感じがして良かったです。
ただし、自分で使いたい処理の部分のみ実装した形なので、仕様をフルで網羅できてはいないです。
この辺りのお話はまた後日
参考
Amazon Cognito ユーザープール の Auth API リファレンス - Amazon Cognito
AWS CognitoにGoogleとYahooとLINEアカウントを連携させる - Qiita