Add AzureADv2 plugin

This is the same as the `AzureAD` plugin except:

1. It uses tenant-specific `microsoftonline.com` v2 OAuth2 endpoints
   (hence the name), which means accepting a new Tenant Id argument
2. It uses a space instead of `,` as the scopes separator

Users of multi-tenant apps can provide a Tenant Id of `"common"`. I'm
also not certain if the space-vs-comma scopes separator represents a bug
in the `AzureAD` plugin, or just a difference in the actual v2 APIs.

This inherits the behavior of using email address as the `credIdent`
although this is definitely an `id` field in the User Response. I'm not
sure if there are trade-offs one way or another. Using `id` could mean
transparently handling Azure users changing their email, but I suspect
your identity is implicitly tied to email within Azure anyway, so that
would not be a case we'll ever see.

In the future, we can deprecate the `AzureAD` plugin and suggest users
migrate to this one.
This commit is contained in:
patrick brisbin 2023-04-05 17:34:26 -04:00 committed by Pat Brisbin
parent 8b46e82981
commit ac1e48db97
4 changed files with 92 additions and 2 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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