{-# LANGUAGE OverloadedStrings #-}
module Web.OIDC.Client.IdTokenFlow
(
getAuthenticationRequestUrl
, getValidIdTokenClaims
, prepareAuthenticationRequestUrl
) where
import Control.Monad (when)
import Control.Exception (throwIO, catch)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Aeson (FromJSON)
import qualified Data.ByteString.Char8 as B
import Data.List (nub)
import Data.Maybe (isNothing, fromMaybe)
import Data.Monoid ((<>))
import Data.Text (unpack)
import Data.Text.Encoding (decodeUtf8)
import qualified Jose.Jwt as Jwt
import Network.HTTP.Client (getUri, setQueryString)
import Network.URI (URI)
import Prelude hiding (exp)
import Web.OIDC.Client.Internal (parseUrl)
import qualified Web.OIDC.Client.Internal as I
import Web.OIDC.Client.Settings (OIDC (..))
import Web.OIDC.Client.Tokens (IdTokenClaims (..), validateIdToken)
import Web.OIDC.Client.Types (OpenIdException (..),
Parameters, Scope,
SessionStore (..), State,
openId)
prepareAuthenticationRequestUrl
:: (MonadIO m)
=> SessionStore m
-> OIDC
-> Scope
-> Parameters
-> m URI
prepareAuthenticationRequestUrl store oidc scope params = do
state <- sessionStoreGenerate store
nonce' <- sessionStoreGenerate store
sessionStoreSave store state nonce'
getAuthenticationRequestUrl oidc scope (Just state) $ params ++ [("nonce", Just nonce')]
getValidIdTokenClaims
:: (MonadIO m, FromJSON a)
=> SessionStore m
-> OIDC
-> State
-> m B.ByteString
-> m (IdTokenClaims a)
getValidIdTokenClaims store oidc stateFromIdP getIdToken = do
(state, savedNonce) <- sessionStoreGet store
if state == Just stateFromIdP
then do
when (isNothing savedNonce) $ liftIO $ throwIO $ ValidationException "Nonce is not saved!"
jwt <- Jwt.Jwt <$> getIdToken
sessionStoreDelete store
idToken <- liftIO $ validateIdToken oidc jwt
when (fromMaybe True $ (/=) <$> savedNonce <*> nonce idToken)
$ liftIO
$ throwIO
$ ValidationException "Nonce does not match request."
pure idToken
else liftIO $ throwIO $ ValidationException $ "Incosistent state: " <> decodeUtf8 stateFromIdP
{-# WARNING getAuthenticationRequestUrl "This function doesn't manage state and nonce. Use prepareAuthenticationRequestUrl only unless your IdP doesn't support state and/or nonce." #-}
getAuthenticationRequestUrl
:: (MonadIO m)
=> OIDC
-> Scope
-> Maybe State
-> Parameters
-> m URI
getAuthenticationRequestUrl oidc scope state params = do
req <- liftIO $ parseUrl endpoint `catch` I.rethrow
return $ getUri $ setQueryString query req
where
endpoint = oidcAuthorizationServerUrl oidc
query = requireds ++ state' ++ params
requireds =
[ ("response_type", Just "id_token")
, ("response_mode", Just "form_post")
, ("client_id", Just $ oidcClientId oidc)
, ("redirect_uri", Just $ oidcRedirectUri oidc)
, ("scope", Just . B.pack . unwords . nub . map unpack $ openId:scope)
]
state' =
case state of
Just _ -> [("state", state)]
Nothing -> []