diff --git a/.env.example b/.env.example index f11cf07..c4a63d7 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,10 @@ AUTH0_CLIENT_SECRET=x AZURE_AD_CLIENT_ID=x AZURE_AD_CLIENT_SECRET=x +AZURE_ADV2_TENANT_ID=x +AZURE_ADV2_CLIENT_ID=x +AZURE_ADV2_CLIENT_SECRET=x + BATTLE_NET_CLIENT_ID=x BATTLE_NET_CLIENT_SECRET=x diff --git a/example/Main.hs b/example/Main.hs index 66fdf00..715fa30 100644 --- a/example/Main.hs +++ b/example/Main.hs @@ -14,7 +14,7 @@ import Data.ByteString.Lazy (fromStrict, toStrict) import qualified Data.Map as M import Data.Maybe (fromJust) import Data.String (IsString(fromString)) -import Data.Text (Text) +import Data.Text (Text, pack) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8) import LoadEnv @@ -25,6 +25,7 @@ import Yesod import Yesod.Auth import Yesod.Auth.OAuth2.Auth0 import Yesod.Auth.OAuth2.AzureAD +import Yesod.Auth.OAuth2.AzureADv2 import Yesod.Auth.OAuth2.BattleNet import Yesod.Auth.OAuth2.Bitbucket import Yesod.Auth.OAuth2.ClassLink @@ -119,6 +120,7 @@ mkFoundation = do loadEnv auth0Host <- getEnv "AUTH0_HOST" + azureTenant <- getEnv "AZURE_ADV2_TENANT_ID" appHttpManager <- newManager tlsManagerSettings appAuthPlugins <- sequence @@ -128,6 +130,7 @@ mkFoundation = do -- FIXME: oauth2BattleNet is quite annoying! -- [ loadPlugin oauth2AzureAD "AZURE_AD" + , loadPlugin (oauth2AzureADv2 $ pack azureTenant) "AZURE_ADV2" , loadPlugin (oauth2Auth0Host $ fromString auth0Host) "AUTH0" , loadPlugin (oauth2BattleNet [whamlet|TODO|] "en") "BATTLE_NET" , loadPlugin oauth2Bitbucket "BITBUCKET" diff --git a/src/Yesod/Auth/OAuth2/AzureADv2.hs b/src/Yesod/Auth/OAuth2/AzureADv2.hs new file mode 100644 index 0000000..c768dd9 --- /dev/null +++ b/src/Yesod/Auth/OAuth2/AzureADv2.hs @@ -0,0 +1,82 @@ +{-# LANGUAGE OverloadedStrings #-} +-- | +-- +-- OAuth2 plugin for Azure AD using the new v2 endpoints. +-- +-- * Authenticates against Azure AD +-- * Uses email as credentials identifier +-- +module Yesod.Auth.OAuth2.AzureADv2 + ( oauth2AzureADv2 + , oauth2AzureADv2Scoped + ) where + +import Prelude +import Yesod.Auth.OAuth2.Prelude + +import Data.String +import Data.Text (unpack) + +newtype User = User Text + +instance FromJSON User where + parseJSON = withObject "User" $ \o -> User <$> o .: "mail" + +pluginName :: Text +pluginName = "azureadv2" + +defaultScopes :: [Text] +defaultScopes = ["openid", "profile"] + +oauth2AzureADv2 + :: YesodAuth m + => Text + -- ^ Tenant Id + -- + -- If using a multi-tenant App, @common@ can be given here. + -- + -> Text -- ^ Client Id + -> Text -- ^ Client secret + -> AuthPlugin m +oauth2AzureADv2 = oauth2AzureADv2Scoped defaultScopes + +oauth2AzureADv2Scoped + :: YesodAuth m + => [Text] -- ^ Scopes + -> Text + -- ^ Tenant Id + -- + -- If using a multi-tenant App, @common@ can be given here. + -- + -> Text -- ^ Client Id + -> Text -- ^ Client Secret + -> AuthPlugin m +oauth2AzureADv2Scoped scopes tenantId clientId clientSecret = + authOAuth2 pluginName oauth2 $ \manager token -> do + (User userId, userResponse) <- authGetProfile + pluginName + manager + token + "https://graph.microsoft.com/v1.0/me" + + pure Creds + { credsPlugin = pluginName + , credsIdent = userId + , credsExtra = setExtra token userResponse + } + where + oauth2 = OAuth2 + { oauth2ClientId = clientId + , oauth2ClientSecret = Just clientSecret + , oauth2AuthorizeEndpoint = + tenantUrl "/authorize" `withQuery` [scopeParam " " scopes] + , oauth2TokenEndpoint = tenantUrl "/token" + , oauth2RedirectUri = Nothing + } + + tenantUrl path = + fromString + $ "https://login.microsoftonline.com/" + <> unpack tenantId + <> "/oauth2/v2.0" + <> path diff --git a/yesod-auth-oauth2.cabal b/yesod-auth-oauth2.cabal index 5912497..836e296 100644 --- a/yesod-auth-oauth2.cabal +++ b/yesod-auth-oauth2.cabal @@ -4,7 +4,7 @@ cabal-version: 1.18 -- -- see: https://github.com/sol/hpack -- --- hash: 2aa7c7881c27a03f22715aa6db358fc5fe466f5967a09741265bd0f7a999e2b1 +-- hash: 019ad5ce3ea29fb93dcf2bd6d9bc7870cebe0dabfc1d0dbceb18639a9f715b34 name: yesod-auth-oauth2 version: 0.7.0.3 @@ -41,6 +41,7 @@ library Yesod.Auth.OAuth2 Yesod.Auth.OAuth2.Auth0 Yesod.Auth.OAuth2.AzureAD + Yesod.Auth.OAuth2.AzureADv2 Yesod.Auth.OAuth2.BattleNet Yesod.Auth.OAuth2.Bitbucket Yesod.Auth.OAuth2.ClassLink