C#: ASP.NET Core MVCのアプリでOneDriveのOAuthを使う

 ASP.NET Core MVCアプリでOneDriveのAPIを使いたいので、OAuthでトークンを取ってくるという使い始めの部分までをまとめる。

 まずアプリをOneDriveのDevCenterに登録する。
https://apps.dev.microsoft.com/

 サインインするとその先の登録アプリ一覧画面の右上にアプリ追加がある。



 必要情報の入力と取得。ClienIDとClientSecret(PasswordとPrivateKeyがあるがPasswordのほう)、入力したリダイレクトURIをメモ。認証コード取得時にリダイレクトURIも渡さないといけないのだが、それが間違っているとコードが降りてこない。


 DevCenterへのアプリ登録ができたらあとはC#コード。OneDriveでアプリの仕様を許諾して認証コードを持って帰ってきたユーザの処理。
[Route("OAuth")]

public async Task<string> AuthUserAsync()
{
var client_id = xxx;
var redirect_uri = xxx;
var client_secret = xxx;
var code = Request.Query["code"];

var httpClient = new System.Net.Http.HttpClient();

// まず持って帰ってきた認証コードを使ってトークンを取得する
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{ "client_id", client_id },
// ↓スラッシュとか入ってるだろうけどエスケープせずに入れてOK
{ "redirect_uri", redirect_uri },
{ "client_secret", client_secret },
{ "code", code },
{ "grant_type", "authorization_code" },
});
var codeResponse = await httpClient.PostAsync("https://login.live.com/oauth20_token.srf", content);
var codeResponseBody = await codeResponse.Content.ReadAsStringAsync();
var jsonObj = JsonConvert.DeserializeObject<Dictionary<string, string>>(codeResponseBody);
var token = jsonObj["access_token"];

// 取得したトークンを使ってOneDriveにユーザ情報を要求する
var uri = $"https://api.onedrive.com/v1.0/drive?access_token={token}";
var tokenResponse = await httpClient.GetAsync(uri);
var tokenResponseBody = await tokenResponse.Content.ReadAsStringAsync();

// ユーザを確認できる情報が得られたのでごにょごにょする
// Foo(tokenResponseBody);

// return tokenResponseBody;

}
comment: 0

PythonアプリにGithubのOAuthを使う

 Web上に置いている自分の読書メモがある。これの環境移植を最近行ったのだが、せっかくなので認証をGithubのOauthを使うようにしてみた。その流れ。

 まずGithubでアプリを登録する。個人設定を開く。


 メニューからOAuthアプリを選ぶ。


 新しくアプリの認証を通す。


 必要な情報の入力。


 一覧にもどってアプリの認証情報をメモ。必要なのはClientIDとClientSecret。


 ここまでで事前準備は終了で、あとのやることはアプリ側。
 OAuthでの認証の流れは…
1.自サイトのリンクからGithub(あるいはOAuthを持っているほかのサイト)へ行ってもらう(ワンクリック)
2.Githubで、自サイトがそこの認証情報を使わせてもらうことをユーザーに許諾してもらう(ワンクリック)
3.Githubであらかじめ設定しておいたリダイレクト先URIにユーザーが認証コードを持ってリダイレクトされてくる
4.自サイトサーバが認証コードを使ってGithubにトークンを要求
5.トークンが帰ってきたらそれを使って再度自サイトサーバがGithubへ、今度はユーザーの個人認証情報を要求
6.Githubから自サイトサーバが受け取った個人認証情報で認証処理を完了させる
という流れ。
 自サーバが持つべきなのはGithubの指定の認証ページへのリンクと、そこからリダイレクトで帰ってきたときの処理をするハンドラの二つか。
 というわけでリダイレクトでGithubから帰ってきたユーザーの認証処理を行うハンドラの流れを書いておく。

 まず認証コードを使ってトークン取得。
        code = self.get_argument("code", None)

client_id = GITHUB_CLIENT
if code:
url = "https://github.com/login/oauth/access_token"
secret = GITHUB_SECRET
data = {
"code": code,
"client_id": client_id,
"client_secret": secret
}
p_data = urllib.parse.urlencode(data).encode(encoding='ascii')
with urllib.request.urlopen(url, data=p_data) as f:
res = f.read().decode("ascii")
token = res.replace("access_token=", "").replace("&scope=&token_type=bearer", "")


 レスポンスからトークンを抜粋する方法がアレなのは置いておく。次はトークンを使って個人情報をGithubからもらう。
            url = "https://api.github.com/user?access_token=" + token

with urllib.request.urlopen(url) as f:
res = f.read().decode("ascii")

info = json.loads(res)


 JSONで返ってくるので、そこからloginIDなどを参照する。OAuthのためのGithubサーバとのやり取りはこれでひと段落。あとはその情報で認証を与えられるなら処理を続行など。
 以下に認証コードを持って帰ってきたユーザーに認証を与える処理の一連を。
class OauthHandler(tornado.web.RequestHandler):

def get(self):
code = self.get_argument("code", None)
client_id = GITHUB_CLIENT
if code:
url = "https://github.com/login/oauth/access_token"
secret = GITHUB_SECRET
data = {
"code": code,
"client_id": client_id,
"client_secret": secret
}
p_data = urllib.parse.urlencode(data).encode(encoding='ascii')
with urllib.request.urlopen(url, data=p_data) as f:
res = f.read().decode("ascii")
token = res.replace("access_token=", "").replace("&scope=&token_type=bearer", "")

url = "https://api.github.com/user?access_token=" + token
with urllib.request.urlopen(url) as f:
res = f.read().decode("ascii")

info = json.loads(res)
user_id = info["login"]

db = self.settings["db"]
doc = db.user.find_one({"_id":user_id})

if doc is not None:
auth_token = self.generate_token()
dt = datetime.datetime.utcnow()
db.session.insert({"_id":auth_token, "createdAt":dt})
self.set_cookie("markofcain", auth_token, secure=True)
self.redirect("/")
else:
self.write("failed")

else:
url = "https://github.com/login/oauth/authorize?client_id=" + client_id
self.write("<a href='{0}'>Oauth auth</a>".format(url))

def generate_token(self):
""" Generate a 32-char alnum string. 190 bits of entropy. """
alnum = ''.join(c for c in map(chr, range(256)) if c.isalnum())
return ''.join(random.choice(alnum) for _ in range(32))
comment: 0

ArukasにTornadoアプリのコンテナを上げるまで

 さくらのArukasにPythonのTornadoフレームワークを使ったアプリのコンテナを上げようと思った。なのでHelloWorld程度のものを実際に上げるまでをメモとして。


 まずはDockerイメージを作る環境が必要なのでその準備から。ぼくはつい最近までWindowsでDockerを使っていたが、ネットワークの不調からちょっとそれが面倒になった。なのでLinuxMintにDockerを入れたものを開発環境として用意した。DockerのLinuxMintへのインストールは下記コマンドで一発。
> sudo curl -sSL https://get.docker.com/ | sh


 Dockerを用意したらつづいて肝心のWebアプリ。Tornadoでサクッとしたものを用意した。あとでArukasの管理画面からDB接続文字列を入れるので、テストに使うハンドラはありきたりなHelloWorldメッセージではなく、環境変数を読み込んで出力するように。
import os


import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write(os.environ['db_conn'])

def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])


settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}

def main():
app = make_app()
app.listen(80)
tornado.ioloop.IOLoop.current().start()

if __name__ == '__main__':
main()


 アプリが用意できたら次はDockerfile。Webアプリのプロセスはsupervisorを使ってデーモン管理。そうすれば自動で再起動設定とかもできる。
FROM ubuntu


RUN apt-get update
RUN apt-get install -y supervisor && \
apt-get install -y python3-pip
RUN pip3 install motor && \
pip3 install tornado

COPY . /usr/
WORKDIR /usr/

EXPOSE 80
CMD /usr/bin/supervisord -c supervisord.conf


 ついでにsupervisor.conf。これは最低限なので必要に応じて追記。
[supervisord]

nodaemon = true

[program:demoniacmadness]
command = python3 demon.py


 ここまできたらDockerfileに従ってDockerイメージをビルド・・・する前にDocker Hubへ行ってアカウントを取得しておく。イメージの名前に自分のアカウント名を入れる必要があるので。
https://hub.docker.com

 Docker Hubアカウントを入手したら改めてイメージ作成。Dockerfileのあるディレクトリで下記コマンド。
> docker build -t yourAcountName/imageName ./

 Dockerイメージの作成が終わったらそれをHubへプッシュする。まずログイン。
> docker login

 ログインできたらプッシュ。
> docker push yourAcountName/imageName

 あとはArukasで設定するだけ。imageに↑でプッシュしたyourAcountName/imageNameを入れる。最初のほうで書いたWebアプリが環境変数を読み込むものにしておいたので、環境変数を任意のキー、値で設定。その他必要事項をポイポイ入れて保存、そして再生ボタンでデプロイ開始。

アプリ名などがアレなのは当時読んでいたキルケゴールの本から取ったことによる。

 デプロイがこれを書いている時点で相当なネックだった。混雑からか4時間ぐらいトライしていたが全然成功しなかった。ただデプロイが失敗したことがわかるだけなので、混雑によるものかDockerイメージ内の設定ミスかもわからないし。まあまだ無料だし有料化するころにはそういうクオリティになってるだろう。

 デプロイがうまくいったらブラウザでアクセスしてみる。


 Webアプリのコンテナの土台ができたので、ここにぼくの必要な機能を実装していく。
comment: 0

コールバック連鎖

 読書メモWebアプリをNodeで作るのをやめて、Python+Tonado+Motorで作るというメモ。

 読書メモをできるアプリをGoogleAppEngineにのっけてあるのだが、フレームワークがすでにレガシーになっているうえに、そもそもMVCも理解してないうちに作ったものなのでプロジェクトとしてあまりきれいになっていない。つい先日AzureWebSitesでLinuxが公開されたので、これにのっけられるように作り直すかと思いついた。AzureWebSitesのLinuxは今のところベータで使用可能言語がNodeとPHPで、まだWebアプリは作ったことがないNodeを経験しておくかと考えた。
 思いついたのはいいが、実際のところでLinux on AzureWebSitesはWindowsと違って無料プランがない。作成するアプリは個人使用で金をかける必要がまったくないものである。じゃあWindowsでいくかとなってしまった。それでもNodeで作ろうと考えてプロジェクトを用意していたが、実際にコードを書き始めて考え直す必要が出てきた。

 NodeはJSの特色であるイベントドリブンを利用してノンブロッキングな処理を得意とする言語だろう。その特色からコード内にはコールバック関数が生まれやすい。コールバック関数の使用を避ける機能の実装もされているが、Promiseを使うにしろそれはそれでちょっとコードに特色が出てしまう。
 とりあえず従来の書き方でMongoDBからドキュメントを取ってきてビューに渡すコードが下記。
router.get('/', function (req, res) {

var url = "xxxxxx";

// Use connect method to connect to the server
MongoClient.connect(url, function (err, db) {

var col = db.collection("quote");
var quotes = col.find({});
quotes.count(false, function (e, c) {
res.render('index', { title: c });
});

});

});

 取得したい項目が増えてDB接続回数が増えるとコールバック関数によるネストが…。
 PythonやC#でWebアプリを作ってきたぼくにはちょっと特異なコードとなる。これはこれでミニマムなAPIを作るにはよさそうだが、ちょっと規模のあるアプリで、シンプルなMVCの実践がしたい今の状況からするとコールバック関数再考というのは避けたい。…言語選び直し。

 非同期に関してはC#でasync、awaitという素晴らしいソリューションを見た。Python+Tonado+Motorという組み合わせでも、修飾子とyieldを使った非同期処理の興味深い実装を見た。
@gen.coroutine

def do_find_one():
document = yield db.test_collection.find_one({'i': {'$lt': 1}})

Nodeでちょっとやってみたことからコールバック関数は避けたいが、非同期を使ってみるのは面白そうと思っている。C#ではすでにこのブログのエンジンを書いているのでここはPython+Tonado+Motorでいってみようかと思う。その組み合わせでならコールバック関数は避けて従来のPythonコードに近い書き方ができるので、いざとなったらほかのフレームワーク、例えばFlaskなどへの移植も難しくないだろう。

 というわけで読書メモWebアプリをPython+Tonado+Motorで作るというメモ。
comment: 0

Azure: Node.jsアプリ環境の準備

 Node.jsのWebアプリをAzureで動かす。今回はAzureでアプリを置く場所の設定からGithub経由での配置準備まで。Azure+ Node.jsでのDB接続文字列設定も。

 最初に、Node.jsプロジェクトはすでに作ってあり、Githubにそのリポジトリがあるものとする。Node.jsプロジェクトはVisualStudioでも簡単に作れる。

 まずAzureの準備。Azureポータルを開いてWebAppsで一つ開く。


 準備が済んだらアドレスを確認してアクセスしてみる。デフォルトのページが表示される。


 アプリが設置されたら、アプリケーション設定にDB接続文字列を設定しておく。


 Github経由でのデプロイ設定をする。デプロイオプションからGithubを選び、あらかじめ作っておいたリポジトリを選択する。


 今回ぼくは事前に、VisualStudioでexpressフレームワークを使ったプロジェクトを用意してある。今回はDB接続文字列をAzureサーバーから読み出せるかだけ試したい。グローバル変数のprocess.envを経由して読み出す。fooという名前の接続文字列をCustomで用意していた場合は下記のようになる。
process.env.CUSTOMCONNSTR_foo


 あとはNode.jsプロジェクトを動くようにしてGithubへデプロイすれば自動でAzureへもデプロイされる。下の画像はVisualStudioで作ったデフォのexpressプロジェクトに、DB接続文字列を確認のために表示したもの。
comment: 0