hoauth2-2.9.0: src/Network/OAuth2/Experiment/Flows/TokenRequest.hs
{-# LANGUAGE FlexibleContexts #-}
module Network.OAuth2.Experiment.Flows.TokenRequest where
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Except (ExceptT (..), throwE)
import Network.HTTP.Conduit
import Network.OAuth.OAuth2 hiding (RefreshToken)
import Network.OAuth2.Experiment.Pkce
import Network.OAuth2.Experiment.Types
import Network.OAuth2.Experiment.Utils
-------------------------------------------------------------------------------
-- Token Request --
-------------------------------------------------------------------------------
class HasTokenRequestClientAuthenticationMethod a where
getClientAuthenticationMethod :: a -> ClientAuthenticationMethod
-- | Only Authorization Code Grant involves a Exchange Token (Authorization Code).
-- ResourceOwnerPassword and Client Credentials make token request directly.
data NoNeedExchangeToken = NoNeedExchangeToken
class (HasOAuth2Key a, HasTokenRequestClientAuthenticationMethod a) => HasTokenRequest a where
-- Each GrantTypeFlow has slightly different request parameter to /token endpoint.
data TokenRequest a
type ExchangeTokenInfo a
-- | Only 'AuthorizationCode flow (but not resource owner password nor client credentials) will use 'ExchangeToken' in the token request
-- create type family to be explicit on it.
-- with 'type instance WithExchangeToken a b = b' implies no exchange token
-- v.s. 'type instance WithExchangeToken a b = ExchangeToken -> b' implies needing an exchange token
-- type WithExchangeToken a b
mkTokenRequestParam :: a -> ExchangeTokenInfo a -> TokenRequest a
-- | Make Token Request
-- https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
conduitTokenRequest ::
(HasTokenRequest a, ToQueryParam (TokenRequest a), MonadIO m) =>
IdpApplication i a ->
Manager ->
ExchangeTokenInfo a ->
ExceptT TokenResponseError m OAuth2Token
conduitTokenRequest IdpApplication {..} mgr exchangeToken = do
let tokenReq = mkTokenRequestParam application exchangeToken
body = unionMapsToQueryParams [toQueryParam tokenReq]
if getClientAuthenticationMethod application == ClientAssertionJwt
then do
resp <- ExceptT . liftIO $ do
req <- uriToRequest (idpTokenEndpoint idp)
let req' = urlEncodedBody body (addDefaultRequestHeaders req)
handleOAuth2TokenResponse <$> httpLbs req' mgr
case parseResponseFlexible resp of
Right obj -> return obj
Left e -> throwE e
else doJSONPostRequest mgr (mkOAuth2Key application) (idpTokenEndpoint idp) body
-------------------------------------------------------------------------------
-- PKCE --
-------------------------------------------------------------------------------
-- | Make Token Request (PKCE)
-- https://datatracker.ietf.org/doc/html/rfc7636#section-4.5
conduitPkceTokenRequest ::
(HasTokenRequest a, ToQueryParam (TokenRequest a), MonadIO m) =>
IdpApplication i a ->
Manager ->
(ExchangeTokenInfo a, CodeVerifier) ->
ExceptT TokenResponseError m OAuth2Token
conduitPkceTokenRequest IdpApplication {..} mgr (exchangeToken, codeVerifier) =
let req = mkTokenRequestParam application exchangeToken
key = mkOAuth2Key application
clientSecretPostParam =
if getClientAuthenticationMethod application == ClientSecretPost
then clientSecretPost key
else []
body =
unionMapsToQueryParams
[ toQueryParam req
, toQueryParam codeVerifier
]
++ clientSecretPostParam
in doJSONPostRequest mgr key (idpTokenEndpoint idp) body