パスワード認証(Yesod.Auth.HashDB)
YesodのID・パスワードの認証
一般的なログインIDとパスワードによる認証の実装 yesod-auth-hashdb というYesodのpluginを利用する、モデルに認証テーブルを作成し ログインIDとパスワードのフィールドを利用して認証を実装する。
実装する画面は以下の2つ。
ログインフォーム
認証前のログイン画面
ログイン後の画面
認証完了後のログイン画面
yesod-auth-hashdbプラグインの追加
package.yamlのdependenciesへyesod-auth-hashdbを追加する。
...
dependencies:
...
- yesod-auth-hashdb
...
認証のアカウント
データベースへ作成する認証用のアカウント情報を保存するモデル。
AdminUsr
adminId Int sqltype=bigint default=nextval('admin_usr_admin_id_seq')
loginId Text sqltype=varchar(256)
password Text Maybe sqltype=varchar(512)
validFlag Bool sqltype=boolean
createTime UTCTime sqltype=timestamptz
updateTime UTCTime sqltype=timestamptz default=now()
version Int sqltype=integer
UniAdminUsrLoginId loginId
Primary adminId
deriving Typeable Show
ログインフォームのための設定
カスタムフォームを利用するための、 authHashDBWithForm と認証元のモデルを決めるための HashDBUser をインポートする。
import Yesod.Auth.HashDB (authHashDBWithForm, HashDBUser(..))
フォームバリデーション
mreq関数と合わせて利用し、 check関数 を利用して合否を判定する、check関数 が Right a を返せば成功、 Left b を返したら失敗となる。 check関数 に モナド版の checkM関数 があり、チェックの中でIOなどを発行したい場合に利用できる。
adminLoginIdField :: Int -> Field Handler Text
adminLoginIdField loginIdLen = check (validateLoginId loginIdLen) textField
validateLoginId :: Int -> Text -> Either Text Text
validateLoginId loginIdLen loginId
| T.length(loginId) > loginIdLen = Left errorMessage
| otherwise = Right loginId
認証バックエンドの設定
認証にログインIDとパスワードを利用すためのプラグイン設定を行う、 Yesod Auth のインスタンス宣言の authPlugins関数 で指定する。 authHashDBWithForm loginPage でカスタムログインページが出力されるように指定し、ユーザーIDとして利用するモデルのカラムを指定する、注意点としてそのカラムにユニーク属性が必要となる。
instance YesodAuth App where
...
authPlugins :: App -> [AuthPlugin App]
= [authHashDBWithForm loginPage (Just . UniAdminUsrLoginId)] authPlugins app
アプリケーションの認証IDをモデルで定義された認証IDへシノニムとして定義する。
instance YesodAuth App where
type AuthId App = AdminUsrId
..
認証処理
yesod-auth-hashdb を利用した認証の場合、実際のログインIDとパスワードの認証は yesod-auth-hashdb 内の関数で実行されるため、 この authenticate関数 は認証が成功した後に呼び出される。なので、 Nothing のコードは通らない。実際にここで行っていることは認証成功ごの追加処理となっている。
instance YesodAuth App where
...
authenticate :: (MonadHandler m, HandlerSite m ~ App)
=> Creds App -> m (AuthenticationResult App)
= liftHandler $ runDB $ do
authenticate creds <- getBy $ UniAdminUsrLoginId $ credsIdent creds
x case x of
Just (Entity uid usr) ->
case adminUsrValidFlag usr of
False -> do
-- アカウント停止中
return $ UserError InvalidLogin
True -> do
-- 正常なログイン
updateAdminLoginTime uidreturn $ Authenticated uid
-- yesod-auth-hashdb内部で処理してしまうのでここは通らない
Nothing -> return $ UserError $ IdentifierNotFound "Admin user not found: "
認証エラー時のメッセージは、「UserError InvalidLogin」 を経由して出力される、 yesod-auth-hashdb を利用した場合のエラーメッセージはプラグイン側が出力するので不要となっている(メッセージを変更するのが少し面倒)
認証の必要なURL
認証が必要なURLに対するアクセスチェックを行う、EntR のように isAuthenticated を利用し、認証されていない場合、AuthResult 型の AuthenticationRequired を返すことによって、ログインフォームへ転送する。
EntR _ = isAuthenticated
isAuthorized ...
-- 認証されているかのチェック
isAuthenticated :: Handler AuthResult
= do
isAuthenticated <- maybeAuthId
muid return $ case muid of
Nothing -> AuthenticationRequired
Just _ -> Authorized
..
認証が必要でないURLへのアクセスは TopR や AuthR のように Authorizedを返す。
TopR _ = return Authorized
isAuthorized AuthR _) _ = return Authorized isAuthorized (
出力メッセージのロケール変更
AuthMessage で定義されるディフォルトのメッセージのロケールが en になっているため日本語に切り替えたい場合は以下のように renderAuthMessage を定義する。
instance YesodAuth App where
-- AuthMessageのロケールを日本語に設定する
renderAuthMessage :: master -> [Text] -> AuthMessage -> Text
renderAuthMessage _ _ = japaneseMessage
認証情報
認証したユーザー情報は maybeAuthPair関数 などで取得する、その他、 maybeAuth間数 、 requireAuthPair関数 などがある。
defaultLayout :: Widget -> Handler Html
= do
defaultLayout widget <- getYesod
master ...
<- maybeAuthPair muser
ログイン・ログアウト後のURLの設定
認証完了後のリダイレクト先URLの設定を行う場合は loginDest関数 を設定しログイン後の Route を指定する。
instance YesodAuth App where
...
loginDest :: App -> Route App
= EntR
loginDest _ ...
ログアウト後のリダイレクト先URLの設定を行う場合は logoutDest関数 を設定しログアウト後の Route を指定する。
instance YesodAuth App where
...
logoutDest :: App -> Route App
= TopR
logoutDest _ ...
認証後に、認証前にアクセスしようとしていたURLへ自動的にリダイレクトさせたい場合、 redirectToReferer関数 を設定する。
instance YesodAuth App where
...
-- ログイン成功時、遷移先URLを保存したい場合Trueを設定する
redirectToReferer :: App -> Bool
= True
redirectToReferer _ ...
ビルド時の警告
ビルド実行時に Foundation.hs で warning: [-Worphans] が出力される、 instance宣言 を AdminUsr の型定義時に行えないことが原因。
stack build
$ ..
/home/cuomo/Code/yesod/hashauth/src/Foundation.hs:209:1: warning: [-Worphans]
Orphan instance: instance HashDBUser AdminUsr
To avoid this
move the instance declaration to the module of the class or of the type, or
wrap the type with a newtype and declare the instance on the new type.
|
209 | instance HashDBUser AdminUsr where
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...
...
得にに必要な対応ではないが気になる人は、 Foundation.hs ファイルの先頭の拡張宣言へ {-# OPTIONS_GHC -fno-warn-orphans #-} を追加することで出力が止まる。
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
...
サンプルコード
サンプルコードはguthubへ上げてあるのでどうぞ。
Posted on 2020-12-06 08:06:47