From be81258ec04915acc5a431ed933d8d36b9090f24 Mon Sep 17 00:00:00 2001 From: "William R. Arellano" Date: Tue, 18 Apr 2023 15:17:20 -0500 Subject: [PATCH] feat(okta): add okta plugin --- src/Yesod/Auth/OAuth2/Okta.hs | 102 ++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/Yesod/Auth/OAuth2/Okta.hs diff --git a/src/Yesod/Auth/OAuth2/Okta.hs b/src/Yesod/Auth/OAuth2/Okta.hs new file mode 100644 index 0000000..b2514d3 --- /dev/null +++ b/src/Yesod/Auth/OAuth2/Okta.hs @@ -0,0 +1,102 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- | +-- Module: Yesod.Auth.OAuth2.Okta +-- +-- OAuth2 plugin for +-- +-- -- * Authenticates against a specific Okta application +-- -- * Uses Okta sub as user id +module Yesod.Auth.OAuth2.Okta + ( oauth2Okta, + oauth2OktaWithScopes, + defaultOktaScopes, + pluginName, + User (..), + ) +where + +import Data.Aeson as Aeson +import Data.ByteString (ByteString) +import Yesod.Auth.OAuth2.Prelude +import Prelude + +-- | Okta User's info: https://developer.okta.com/docs/reference/api/oidc/#userinfo +newtype User = User Text + +instance FromJSON User where + parseJSON = withObject "User" $ \o -> User <$> o .: "sub" + +-- | Default scopes +defaultOktaScopes :: [Text] +defaultOktaScopes = ["openid"] + +-- | Plugin name for callback routes and session data. +pluginName :: Text +pluginName = "okta" + +-- | Creates an Okta 'AuthPlugin' for application using the default scopes. +oauth2Okta :: + YesodAuth m => + -- | The host address of the Okta application (absolute) + URI -> + -- | Client ID of the Okta application + Text -> + -- | Client Secret of the Okta application + Text -> + -- | The authorization server + ByteString -> + AuthPlugin m +oauth2Okta = oauth2OktaWithScopes defaultOktaScopes + +-- | Creates an Okta 'AuthPlugin' for application with access to the provided scopes. +oauth2OktaWithScopes :: + YesodAuth m => + -- | The scopes accessible to the 'AuthPlugin' + [Text] -> + -- | The host address of the Okta application (absolute) + URI -> + -- | Client ID of the Okta application + Text -> + -- | Client Secret of the Okta application + Text -> + -- | The authorization server + ByteString -> + AuthPlugin m +oauth2OktaWithScopes scopes host clientId clientSecret authorizationServer = + authOAuth2 pluginName oauth2 $ \manager token -> do + (User uid, userResponse) <- + authGetProfile + pluginName + manager + token + (host `withPath` (mkEndpointSegment authorizationServer "userinfo")) + pure + Creds + { credsPlugin = pluginName, + credsIdent = uid, + credsExtra = setExtra token userResponse + } + where + oauth2 = + OAuth2 + { oauth2ClientId = clientId, + oauth2ClientSecret = Just clientSecret, + oauth2AuthorizeEndpoint = + host + `withPath` (mkEndpointSegment authorizationServer "authorize") + `withQuery` [scopeParam " " scopes], + oauth2TokenEndpoint = host `withPath` (mkEndpointSegment authorizationServer "token"), + oauth2RedirectUri = Nothing + } + +-- | Helper function for creating an endpoint path segment for the given authorization server +-- and endpoint. +mkEndpointSegment :: + -- | Authorization server ID + ByteString -> + -- | Endpoint + ByteString -> + ByteString +mkEndpointSegment authorizationServer endpoint = + "/oauth2/" <> authorizationServer <> "/v1/" <> endpoint