LoginSignup
4
2

More than 5 years have passed since last update.

Yahoo! JapanのOpenID Connectをyesodで試す

Last updated at Posted at 2017-02-20

Yahoo! JapanのOpenID Connectをyesodで試す

はじめに

SNSなどで、ほかのアプリケーションに、プロフィールの閲覧などを許す機能がある。これが認可であり、OAuthという仕組みで、それが実現される。このOAuthという仕組みは認可のためのわくぐみであり、本人確認(認証)のための機能ではない。

「認可をあたえられるのは本人だけである」という理屈で、この「認可」を本人確認に使う場合がある。しかし、これをすると、大きなセキュリティホールを作ってしまう。

単なるOAuth 2.0を認証に使うと、車が通れるほどのどでかいセキュリティ・ホールができる

OAuth 2.0のわくぐみのうえで、きちんとした本人確認(認証)をおこなう仕組みがある。それがOpenID Connectだ。

何をするか

HaskellによるWebアプリケーションフレームワークであるYesodを使って、Yahoo! JapanのOpenID Connectの機能を試してみる。つぎのページの内容をYesodで実装した。

OpenID Connect体験

このページでは、おもにコードの紹介をする。細かい説明については上記のページを参照してほしい。
上記のページを参照したうえで、Yesodで試そうと思ったときに、この記事を読むのがいいかと思う。

前提

HaskellのStackはインストールされているものとする。
また、Yahoo IDは取得ずみとする。

Yesodによるテストサーバを作る

Yesodでテストサーバを動かす。

% stack new testOpenID yesod-simple && cd testOpenID
% stack install yesod-bin cabal-install --install-ghc
% stack build
% stack exec -- yesod devel

これでhttp://localhost:3000/ に接続する。もしかすると、うまくいかないかもしれない。そんなときには、stack ... develしたターミナルでEnterを入力すると、うまくいくかもしれない。

Yahoo! Japanにアプリケーションを登録する

https://e.developer.yahoo.co.jp/dashboard/ に接続し、「新しいアプリケーションを開発」ボタンをクリックする。

アプリケーション名を好きな名前に設定する。サイトURLをhttp://localhost:3000/ として、ガイドラインへの同意をクリックして、「確認」ボタンを押す。登録をクリックする。

アプリケーションIDとシークレットが必要になるのでメモしておこう。
ファイルy_clientId.txtとy_clientSecret.txtを、それぞれの内容で作成しておく。

ダミーのページを用意する

ログイン後のページとしてダミーのページを用意する。

% vi testOpenID.cabal

libraryのexposed-modules:のところの最後を、つぎのようにする。

Handler.Home
Handler.Comment
Handler.YLogined

Handler.YLoginedを追加した。
config/routesに追加する。

/ylogined YLoginedR GET

Handler.Homeモジュールをコピーする。

% cp Handler/Home.hs Handler/YLogined.hs

モジュール宣言をHandler.HomeからHandler.YLoginedにする。
getHomeRをgetYLoginedRに置き換える。
postHomeRは削除する。

% vi Application.hs
(import Handler.YLoginedを追加する)

これで、stack exec -- yesod develをする。
http://localhost:3000/ylogined に接続してみよう。

リダイレクトする

http://localhost:3000/ への接続を、Yahoo! Japanの認可用のページにリダイレクトする。
GETメソッドでいくつかのパラメータをわたす。

まずは、Data.Textモジュールなどを導入する。

% vi Hundler/Home.hs
import qualified Data.Text as Text
import qualified Data.Text.IO as Text

getHomeRの内容をリダイレクトに書き換える。

% vi Hundler/Home.hs
getHomeR = do
        yClient <- lift $
                Text.concat . Text.lines <$> Text.readFile "y_clientId.txt"
        redirect $
                "https://auth.login.yahoo.co.jp/yconnect/v1/authorization?" <>
                        "response_type=code+id_token&" <>
                        "scope=openid+profile&" <>
                        "client_id=" <> yClientId <> "&state=hogeru&" <>
                        "nonce=abcdefghijklmnop&" <>
                        "redirect_uri=http://localhost:3000/logined"

stack exec -- yesod develとしhttp://localhost:3000/ に接続すると、redirect_uri is invalidというエラーとなる。

https://e.developer.yahoo.co.jp/dashboard/ に接続し、編集ボタンをクリックする。
コールバックURLをhttp://localhost:3000/ylogined に変更する。

パラメータを確認する

Yahoo! Japanのページにリダイレクトすると、指定したページに、さらに、リダイレクトされる。そのときcodeとstateというパラメータがわたされる。これを表示してみよう。

% vi Hundler/YLogined.hs
getYLoginedR = do
        Just code <- lookupGetParam "code"
        Just state <- lookupGetParam "state"
        print code
        print state
        (formWidget, formEnctype) <- generateFormPost sampleForm
        ...

アクセストークン、IDトークンを取得

Network.HTTP.Simpleモジュールを導入する。

% vi Hundler/YLogined.hs
import Network.HTTP.Simple

ByteStringをあつかうので、モジュールを導入する。

import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC

Base64エンコードが必要なのでモジュールを導入する。

import qualified Data.ByteString.Base64.URL as B64

これらのモジュールを使うために.cabalファイルのlibraryセクションの依存パッケージのところにつぎのように追加する。

% vi testOpenID.cabal
, base64-bytestring

ファイルからクライアントIDとクライアントシークレットを読み込む。

% vi Hundler/YLogined.hs
...
print state
yClientId <-
        lift $ BS.concat . BSC.lines <$> BS.readFile "y_clientId.txt"
yClientSecret <-
        lift $ BS.concat . BSC.lines <$> BS.readFile "y_clientSecret.txt"

アクセストークン、IDトークンを要求するための、Requestを作成する。

% vi Hundler/YLogined.hs
initReq <-
        parseRequest "https://auth.login.yahoo.co.jp/yconnect/v1/token"
let     yClientIdSecret = B64.encode $ yClientId <> ":" <> yClientSecret
        req = setRequestHeader "Content-Type"
                ["application/x-www-form-urlencoded"]
                initReq { method = "POST" }
        req' = setRequestHeader "Authorization"
                ["Basic " <> yClientIdSecret] req
        req'' = setRequestBody (RequestBodyBS $
                "grant_type=authorization_code&code=" <>
                encodeUtf8 code <>
                "&redirect_uri=http://localhost:3000/ylogined") req'

できあがったRequestでYahoo! Japanに接続して、応答を表示する。

rBody <- getResponseBody <$> httpLBS req''
print rBody

JSON形式のデータをハッシュに読み込む。まずは、モジュールを導入する。

import qualified Data.Aeson as Aeson
import qualified Data.HashMap.Lazy as HML

ハッシュに読み込んで、アクセストークンとIDトークンとを取り出す。

let     Just resp = Aeson.decode rBody :: Maybe Aeson.Object
print $ keys resp
let     Just (String at) = HML.lookup "access_token" resp
        Just (String it) = HML.lookup "id_token" resp
print at
print it

IDトークンの内容を見る

必要なモジュールを導入する。

import qualified Data.Text as Text
import qualified Data.ByteString.Lazy as LBS

IDトークンをヘッダー部、ペイロード部、シグネチャ部にわけて、前2者のなかみを見てみる。

let     [hd, pl, sg] = Text.splitOn "." it
        [Just hdd, Just pld] = map
                ((Aeson.decode :: LBS.ByteString -> Maybe Aeson.Object)
                        . LBS.fromStrict
                        . either (error . show) id
                        . B64.decode . encodeUtf8)
        [hd, pl]
print hdd
print pld

IDトークンの内容を検証する

cryptohashパッケージを追加する。

% vi testOpenID.cabal
library
    ...
    build-depends:
        ...
        , base64-bytestring
        , cryptohash

必要なモジュールを導入する。

% vi Handler/YLogined.hs
import Crypto.MAC.HMAC
import qualified Crypto.Hash.SHA256 as SHA256

署名を確認する。

% vi Hansler/YLogined.hs
        putStrLn sg
        lift . BSC.putStrLn . B64.encode
                . hmac SHA256.hash 64 yClientSecret
                $ encodeUtf8 hd <> "." <> encodeUtf8 pl

その他のプロフィールの内容などを入手する

Yahoo! Japanからユーザの情報を取得する。

initReq2 <- parseRequest $
        "https://userinfo.yahooapis.jp/yconnect/v1/attribute?schema=openid"
let     req2 = setRequestHeader
                "Authorization" ["Bearer " <> encodeUtf8 at] initReq2
rBody2 <- getResponseBody <$> httpLBS req2
let     Just json2 = Aeson.decode rBody2 :: Maybe Aeson.Object
mapM_ print $ HML.toList json2

コード

GitHubに公開してあります。

これの/yesod/testOpenID以下です。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2