-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
Copy pathOkta.hs
99 lines (90 loc) · 2.95 KB
/
Okta.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{-# LANGUAGE QuasiQuotes #-}
-- https://developer.okta.com/docs/reference/api/oidc/#request-parameters
-- Okta Org AS doesn't support consent
-- Okta Custom AS does support consent via config (what scope shall prompt consent)
-- | [Okta OIDC & OAuth2 API](https://developer.okta.com/docs/reference/api/oidc/)
module Network.OAuth2.Provider.Okta where
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Except (ExceptT (..))
import Data.Aeson
import Data.Aeson qualified as Aeson
import Data.Bifunctor
import Data.ByteString.Contrib
import Data.ByteString.Lazy.Char8 qualified as BSL
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text.Lazy as TL
import Data.Time
import GHC.Generics
import Jose.Jwa
import Jose.Jwk
import Jose.Jws
import Jose.Jwt
import Network.HTTP.Conduit (Manager)
import Network.OAuth.OAuth2
import Network.OAuth2.Experiment
import Network.OAuth2.Provider
import Network.OIDC.WellKnown
import URI.ByteString.QQ
sampleOktaAuthorizationCodeApp :: AuthorizationCodeApplication
sampleOktaAuthorizationCodeApp =
AuthorizationCodeApplication
{ acClientId = ""
, acClientSecret = ""
, acScope = Set.fromList ["openid", "profile", "email"]
, acAuthorizeState = "CHANGE_ME"
, acAuthorizeRequestExtraParams = Map.empty
, acRedirectUri = [uri|http://localhost|]
, acName = "sample-okta-authorization-code-app"
, acTokenRequestAuthenticationMethod = ClientSecretBasic
}
fetchUserInfo ::
(MonadIO m, HasUserInfoRequest a, FromJSON b) =>
IdpApplication i a ->
Manager ->
AccessToken ->
ExceptT BSL.ByteString m b
fetchUserInfo = conduitUserInfoRequest
mkOktaIdp ::
MonadIO m =>
-- | Full domain with no http protocol. e.g. @foo.okta.com@
Text ->
ExceptT Text m (Idp Okta)
mkOktaIdp domain = do
OpenIDConfiguration {..} <- fetchWellKnown domain
pure $
Idp
{ idpUserInfoEndpoint = userinfoEndpoint
, idpAuthorizeEndpoint = authorizationEndpoint
, idpTokenEndpoint = tokenEndpoint
, idpDeviceAuthorizationEndpoint = Just deviceAuthorizationEndpoint
}
mkOktaClientCredentialAppJwt ::
Jwk ->
ClientId ->
Idp i ->
IO (Either Text Jwt)
mkOktaClientCredentialAppJwt jwk cid idp = do
now <- getCurrentTime
let cidStr = unClientId cid
let payload =
bsToStrict $
Aeson.encode $
Aeson.object
[ "aud" .= idpTokenEndpoint idp
, "exp" .= tToSeconds (addUTCTime (secondsToNominalDiffTime 300) now) -- 5 minutes expiration time
, "iat" .= tToSeconds now
, "iss" .= cidStr
, "sub" .= cidStr
]
first (TL.pack . show) <$> jwkEncode RS256 jwk (Claims payload)
where
tToSeconds = formatTime defaultTimeLocale "%s"
data OktaUser = OktaUser
{ name :: Text
, preferredUsername :: Text
}
deriving (Show, Generic)
instance FromJSON OktaUser where
parseJSON =
genericParseJSON defaultOptions {fieldLabelModifier = camelTo2 '_'}