diff --git a/atlassian-connect-core.cabal b/atlassian-connect-core.cabal
--- a/atlassian-connect-core.cabal
+++ b/atlassian-connect-core.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.7.2.0
+version:             0.8.0.0
 
 -- A short (one-line) description of the package.
 synopsis:            Atlassian Connect snaplet for the Snap Framework and helper code.
@@ -65,6 +65,7 @@
    -- TODO do page tokens even belong here? Should they even be a separate library per service?
   exposed-modules:    Snap.AtlassianConnect
                     , Snap.AtlassianConnect.HostRequest
+                    , Snap.AtlassianConnect.OAuth
 
   -- Modules included in this library but not exported.
   other-modules:      Data.AesonHelpers
@@ -80,6 +81,7 @@
                     , Snap.AtlassianConnect.Data
                     , Snap.AtlassianConnect.Instances
                     , Snap.AtlassianConnect.LifecycleResponse
+                    , Snap.AtlassianConnect.NetworkCommon
                     , Snap.AtlassianConnect.PageToken
                     , Snap.AtlassianConnect.QueryStringHash
                     , Snap.AtlassianConnect.Routes
@@ -92,7 +94,7 @@
 
   -- Other library packages from which modules are imported.
   build-depends:       base >=4.7 && < 5
-                       , atlassian-connect-descriptor >= 0.2
+                       , atlassian-connect-descriptor >= 0.4.5
                        , jwt                >= 0.6
                        , aeson              >=  0.7.0.3
                        , bytestring         >= 0.9
diff --git a/src/Snap/AtlassianConnect/HostRequest.hs b/src/Snap/AtlassianConnect/HostRequest.hs
--- a/src/Snap/AtlassianConnect/HostRequest.hs
+++ b/src/Snap/AtlassianConnect/HostRequest.hs
@@ -4,7 +4,7 @@
 {-|
 Module      : Snap.AtlassianConnect.HostRequest
 Description : Allows you to easily make requests from your Atlassian Connect addon to the Host Application (like Jira or Confluence).
-Copyright   : (c) Robert Massioli, 2014
+Copyright   : (c) Robert Massioli, 2014-2017
 License     : APACHE-2
 Maintainer  : rmassaioli@atlassian.com
 Stability   : experimental
@@ -33,7 +33,7 @@
     , hostPutRequest
     , hostPutRequestExtended
     , StdMethod(..)
-    , ProductErrorResponse(..)
+    , AC.ProductErrorResponse(..)
     -- * Request Modifiers
     , addHeader
     , setPostParams
@@ -69,82 +69,87 @@
 import qualified Snap.AtlassianConnect.Data            as AC
 import qualified Snap.AtlassianConnect.Instances       as AC
 import qualified Snap.AtlassianConnect.QueryStringHash as QSH
+import qualified Snap.AtlassianConnect.NetworkCommon   as AC
 import qualified Snap.AtlassianConnect.Tenant          as AC
 import qualified Snap.Snaplet                          as SS
 import qualified Web.JWT                               as JWT
 
-type HttpResponseCode = Int
-
--- | If you fail to properly communicate with the Host application then you will get a product error response back.
-data ProductErrorResponse = ProductErrorResponse
-   { perCode    :: HttpResponseCode -- ^ The HTTP error code.
-   , perMessage :: T.Text           -- ^ The error message.
-   } deriving (Show, Generic)
-
 -- | This is a convinience method that calls 'hostRequest' as a GET method.
 --
 -- Here is an example of it being used to make a call for user details:
 --
--- > jiraUserDetailsResponse <- hostGetRequest myConnectTenant "/rest/api/2/user" [("username", Just "admin")] mempty
+-- > jiraUserDetailsResponse <- hostGetRequest myConnectTenant Nothing "/rest/api/2/user" [("username", Just "admin")] mempty
 --
 -- This example assumes that you are using the OverloadedStrings LANGUAGE pragma and that you don't need to modify the request.
 -- You might want to might want to modify the request for multiple reasons. Too add proxy details for example.
-hostGetRequest :: FromJSON a => AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse a)
+hostGetRequest :: FromJSON a => AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse a)
 hostGetRequest = hostRequestWithContent GET
 
 -- | This is a convinience method that calls 'hostRequest' as a POST method and requires that the
 -- resource returns content.
-hostPostRequest :: FromJSON a => AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse a)
+hostPostRequest :: FromJSON a => AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse a)
 hostPostRequest = hostRequestWithContent POST
 
 -- | This is the same method as hostPostRequest except that if HTTP 204 No Content is returned then
 -- you will get nothing instead of an error.
-hostPostRequestExtended :: FromJSON a => AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse (Maybe a))
+hostPostRequestExtended :: FromJSON a => AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse (Maybe a))
 hostPostRequestExtended = hostRequest POST
 
 -- | This is a convinience method that calls 'hostRequest' as a PUT method.
-hostPutRequest :: FromJSON a => AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse a)
+hostPutRequest :: FromJSON a => AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse a)
 hostPutRequest = hostRequestWithContent PUT
 
 -- | This is the same method as hostPutRequest except that if HTTP 204 No Content is returned then
 -- you will get nothing instead of an error.
-hostPutRequestExtended :: FromJSON a => AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse (Maybe a))
+hostPutRequestExtended :: FromJSON a => AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse (Maybe a))
 hostPutRequestExtended = hostRequest PUT
 
-hostRequestWithContent :: FromJSON a => StdMethod -> AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse a)
-hostRequestWithContent httpMethod tenant productRelativeUrl queryParams modifications = errorNoContent <$> hostRequest httpMethod tenant productRelativeUrl queryParams modifications
+hostRequestWithContent :: FromJSON a => StdMethod -> AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse a)
+hostRequestWithContent httpMethod tenant accessToken productRelativeUrl queryParams modifications = errorNoContent <$> hostRequest httpMethod tenant accessToken productRelativeUrl queryParams modifications
 
-errorNoContent :: Either ProductErrorResponse (Maybe a) -> Either ProductErrorResponse a
+errorNoContent :: Either AC.ProductErrorResponse (Maybe a) -> Either AC.ProductErrorResponse a
 errorNoContent (Left x) = Left x
-errorNoContent (Right Nothing) = Left (ProductErrorResponse 204 "We expected to get content back from this resource but instead we recieved no content.")
+errorNoContent (Right Nothing) = Left (AC.ProductErrorResponse 204 "We expected to get content back from this resource but instead we recieved no content.")
 errorNoContent (Right (Just x)) = Right x
 
 -- | As an Atlassian Connect application you will want to make requests of the Host Applicaiton (JIRA / Confluence)
 -- so that you can get important information. This function lets you do so by doing most of the heavy lifting of
 -- having to create a JWT token and a Query String Hash. It also asserts that you intended to get a JSON response.
 -- You should use this method or the helper methods whenever you want to make requests of the host application.
-hostRequest :: FromJSON a => StdMethod -> AC.Tenant -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either ProductErrorResponse (Maybe a))
-hostRequest standardHttpMethod tenant productRelativeUrl queryParams requestModifications = do
+hostRequest :: FromJSON a => StdMethod -> AC.Tenant -> Maybe AC.AccessToken -> B.ByteString -> [(B.ByteString, Maybe B.ByteString)] -> Endo Request -> SS.Handler b AC.Connect (Either AC.ProductErrorResponse (Maybe a))
+hostRequest standardHttpMethod tenant Nothing productRelativeUrl queryParams requestModifications = do
     currentTime <- MI.liftIO P.getPOSIXTime
     pluginKey' <- fmap (CD.pluginKey . AC.connectPlugin) get
     case generateJWTToken pluginKey' currentTime (AC.sharedSecret tenant) standardHttpMethod productBaseUrl url of
-        Nothing -> return . Left $ ProductErrorResponse 500 "Failed to generate a JWT token to make the request. The request was never made: server error."
-        (Just signature) -> MI.liftIO $ runRequest connectionManagerSettings standardHttpMethod url
-            (  addHeader ("Accept","application/json")
+        Nothing -> return . Left $ AC.ProductErrorResponse 500 "Failed to generate a JWT token to make the request. The request was never made: server error."
+        (Just signature) -> MI.liftIO $ runRequest tlsManagerSettings standardHttpMethod url
+            (  addHeader ("Accept", "application/json")
             <> addHeader ("Authorization", jwtPrefix `B.append` T.encodeUtf8 signature)
             <> requestModifications
             )
-            (basicResponder responder)
+            (basicResponder AC.responder)
     where
         jwtPrefix :: B.ByteString
         jwtPrefix = BC.pack "JWT "
 
-        connectionManagerSettings = if "https://" `isPrefixOf` productBaseUrlString then tlsManagerSettings else defaultManagerSettings
-
         url = T.decodeUtf8 $ BC.pack productBaseUrlString `B.append` productRelativeUrl `B.append` renderQuery True queryParams
         productBaseUrlString = show productBaseUrl
         productBaseUrl = AC.getURI . AC.baseUrl $ tenant
+hostRequest standardHttpMethod tenant (Just (AC.AccessToken accessToken)) productRelativeUrl queryParams requestModifications = do
+    MI.liftIO $ putStrLn $ "Using bearer token: " ++ show accessToken
+    MI.liftIO $ runRequest tlsManagerSettings standardHttpMethod url
+        (  addHeader ("Accept", "application/json")
+        <> addHeader ("Authorization", bearerPrefix `B.append` T.encodeUtf8 accessToken)
+        <> requestModifications
+        )
+        (basicResponder AC.responder)
+    where
+        bearerPrefix :: B.ByteString
+        bearerPrefix = BC.pack "Bearer "
 
+        url = T.decodeUtf8 $ BC.pack productBaseUrlString `B.append` productRelativeUrl `B.append` renderQuery True queryParams
+        productBaseUrlString = show . AC.getURI . AC.baseUrl $ tenant
+
 generateJWTToken :: CD.PluginKey -> P.POSIXTime -> T.Text -> StdMethod -> URI -> T.Text -> Maybe T.Text
 generateJWTToken pluginKey' fromTime sharedSecret' method' ourURL requestURL = do
   queryStringHash <- QSH.createQueryStringHash method' ourURL requestURL
@@ -153,8 +158,8 @@
 createClaims :: CD.PluginKey -> P.POSIXTime -> T.Text -> JWT.JWTClaimsSet
 createClaims (CD.PluginKey pluginKey) fromTime queryStringHash = JWT.JWTClaimsSet
     { JWT.iss = JWT.stringOrURI pluginKey
-    , JWT.iat = JWT.intDate fromTime
-    , JWT.exp = JWT.intDate expiryTime
+    , JWT.iat = JWT.numericDate fromTime
+    , JWT.exp = JWT.numericDate expiryTime
     , JWT.sub = Nothing
     , JWT.aud = Nothing
     , JWT.nbf = Nothing
@@ -168,15 +173,6 @@
         -- Our default expiry period when talking to the host product directly
         expiryPeriod :: Minute
         expiryPeriod = 1
-
-responder :: FromJSON a => Int -> BL.ByteString -> Either ProductErrorResponse (Maybe a)
-responder responseCode body
-   | responseCode == 204 = Right Nothing
-   | 200 <= responseCode && responseCode < 300 =
-      case eitherDecode body of
-         Right jsonResponse -> Right . Just $ jsonResponse
-         Left err -> Left $ ProductErrorResponse responseCode (T.pack $ "Could not parse the json response: " ++ show err)
-   | otherwise = Left $ ProductErrorResponse responseCode (T.decodeUtf8 . BL.toStrict $ body)
 
 -- Wrapper around another function
 setPostParams :: [(B.ByteString, B.ByteString)] -> Endo Request
diff --git a/src/Snap/AtlassianConnect/NetworkCommon.hs b/src/Snap/AtlassianConnect/NetworkCommon.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/AtlassianConnect/NetworkCommon.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Snap.AtlassianConnect.NetworkCommon 
+    ( HttpResponseCode
+    , ProductErrorResponse(..)
+    , AccessTokenResponse(..)
+    , AccessToken(..)
+    , AccessTokenType(..)
+    , responder
+    ) where
+
+import           Data.Aeson
+import qualified Data.ByteString.Lazy                  as BL
+import           Data.Maybe                            (fromMaybe)
+import qualified Data.Text                             as T
+import qualified Data.Text.Encoding                    as T
+import           Data.Time.Units
+import           GHC.Generics
+import           Text.Read                             (readMaybe)
+
+type HttpResponseCode = Int
+
+-- | If you fail to properly communicate with the Host application then you will get a product error response back.
+data ProductErrorResponse = ProductErrorResponse
+   { perCode    :: HttpResponseCode -- ^ The HTTP error code.
+   , perMessage :: T.Text           -- ^ The error message.
+   } deriving (Show, Generic)
+
+-- | This represents the response that an OAuth Access token request should expect to recieve from auth.atlassian.io.
+data AccessTokenResponse = AccessTokenResponse
+    { atrAccessToken :: AccessToken -- ^ The access token that can be used in subsequent HTTP requests.
+    , atrExpiresIn :: Second -- ^ The duration until this access token expires and can no longer be used.
+    , atrTokenType :: AccessTokenType -- ^ The type of token that was returned from auth.atlassian.io.
+    }
+
+instance FromJSON AccessTokenResponse where
+    parseJSON = withObject "AccessTokenResponse" $ \v -> AccessTokenResponse 
+        <$> (AccessToken <$> v .: "access_token")
+        <*> (fromInteger <$> v .: "expires_in")
+        <*> ((fromMaybe UnknownAccessTokenType . readMaybe) <$> v .: "token_type")
+
+-- | An access token that can be used in subsequent requests.
+data AccessToken = AccessToken T.Text
+
+-- | The type of access token that was returned from the token generation service.
+data AccessTokenType
+    = Bearer -- ^ An OAuth 2 bearer token.
+    | UnknownAccessTokenType -- ^ A token type that could not be parsed.
+    deriving (Eq, Enum, Read)
+
+responder :: FromJSON a => Int -> BL.ByteString -> Either ProductErrorResponse (Maybe a)
+responder responseCode body
+   | responseCode == 204 = Right Nothing
+   | 200 <= responseCode && responseCode < 300 =
+      case eitherDecode body of
+         Right jsonResponse -> Right . Just $ jsonResponse
+         Left err -> Left $ ProductErrorResponse responseCode (T.pack $ "Could not parse the json response: " ++ show err)
+   | otherwise = Left $ ProductErrorResponse responseCode (T.decodeUtf8 . BL.toStrict $ body)
diff --git a/src/Snap/AtlassianConnect/OAuth.hs b/src/Snap/AtlassianConnect/OAuth.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/AtlassianConnect/OAuth.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{-|
+Module      : Snap.AtlassianConnect.OAuth
+Description : Allows you to impersonate users inside an Atlassian Product (like JIRA or Confluence).
+Copyright   : (c) Robert Massioli, 2017
+License     : APACHE-2
+Maintainer  : rmassaioli@atlassian.com
+Stability   : experimental
+
+By default, all HTTP requests that your add-on makes to an Atlassian application (like JIRA or Confluence) will
+be performed as your add-on user. For example, if your add-on service makes a HTTP request to comment on a JIRA 
+issue then the comment will be made as your add-on. However, if your addon requests the "ACT_AS_USER" scope then
+is is capable of using the OAuth service to generate an "access token" for a particular user on a tenant. This
+access token can be used to make make HTTP requests from your add-on service to an Atlassian product as a user
+inside that product. This allows you to make HTTP requests that can impersonate users on a given instance.
+
+Using our previous example, if you generated an access token for the user "Bob" and then made the same request,
+but with that new access token, then the comment made on the issue would appear to come from Bob.
+
+You can read more about access token on the <https://developer.atlassian.com/cloud/jira/platform/oauth-2-jwt-bearer-token-authorization-grant-type/ official documentation>.
+-}
+module Snap.AtlassianConnect.OAuth 
+    ( requestAccessToken
+    , AC.AccessTokenResponse(..)
+    , AC.AccessToken
+    , AC.AccessTokenType(..)
+    ) where
+
+import           Data.Aeson
+import qualified Data.ByteString.Char8                 as BSC
+import qualified Data.ByteString.Lazy                  as BL
+import qualified Data.Connect.Descriptor               as D
+import           Data.List                             (isPrefixOf)
+import qualified Data.Map                              as M
+import qualified Data.Text                             as T
+import qualified Data.Text.Encoding                    as T
+import qualified Data.Time.Clock.POSIX                 as P
+import           Data.Time.Units                       (Minute)
+import           Data.TimeUnitUTC
+import           Network.Api.Support
+import           Network.HTTP.Client
+import           Network.HTTP.Client.TLS               (tlsManagerSettings)
+import           Network.HTTP.Types
+import           Network.URI
+import           Snap.AtlassianConnect.AtlassianTypes
+import qualified Snap.AtlassianConnect.Data            as AC
+import qualified Snap.AtlassianConnect.Instances       as AC
+import qualified Snap.AtlassianConnect.NetworkCommon   as AC
+import qualified Snap.AtlassianConnect.Tenant          as AC
+import qualified Web.JWT                               as JWT
+
+-- | Request an access token for a tenant and user with the requested scopes. 
+-- 
+-- This token will allow you to post subsequent HTTP requests using the Snap.AtlassianConnect.HostRequest module
+-- as an impersonated user.
+--
+-- The access token that is returned should be reused as much as possible in order to avoid hitting rate limits 
+-- from the token generation service. Each token is specific to the tenant, user and scopes that
+-- it was created for. This means that the token reuse must be tenant, user and scopes specific.
+requestAccessToken 
+    :: AC.Tenant -- ^ The tenant to generate the Access Token for.
+    -> UserKey -- ^ The user key of the user on the tenant that should be impersonated.
+    -> Maybe [D.ProductScope] -- ^ The collection of scopes that will be used for impersonation. Providing none requests all scopes that the add-on provides.
+    -> IO (Either AC.ProductErrorResponse (Maybe AC.AccessTokenResponse)) -- ^ Returns either a HTTP error or the result of attempting to parse the AccessTokenResponse.
+requestAccessToken tenant userKey possibleScopes = do
+    potentialAssertion <- generateJWTAssertion tenant userKey
+    case potentialAssertion of
+        Nothing -> return . Right $ Nothing
+        Just assertion ->
+            runRequest tlsManagerSettings POST url
+                ( addHeader ("Host", "auth.atlassian.io")
+                <> addHeader ("Accept", "application/json")
+                <> addHeader ("Content-Type", "application/x-www-form-urlencoded")
+                <> setBody (renderSimpleQuery False (formParams assertion))
+                )
+                (basicResponder AC.responder)
+    where
+        url = authBaseUrl `T.append` "/oauth2/token"
+
+        formParams :: T.Text -> [(BSC.ByteString, BSC.ByteString)]
+        formParams assertion = 
+            [ ("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
+            , ("assertion", T.encodeUtf8 assertion)
+            ] ++ scopeParam
+
+        scopeParam = case possibleScopes of
+            Nothing -> []
+            Just scopes -> (: []) . (,) "scope" . BSC.intercalate " " . fmap printScope . filter ((/=) D.ActAsUser) $ scopes
+
+printScope :: D.ProductScope -> BSC.ByteString 
+printScope D.Read         = "READ"
+printScope D.Write        = "WRITE"
+printScope D.Delete       = "DELETE"
+printScope D.ProjectAdmin = "PROJECT_ADMIN"
+printScope D.SpaceAdmin   = "SPACE_ADMIN"
+printScope D.Admin        = "ADMIN"
+printScope D.ActAsUser    = "ACT_AS_USER"
+
+generateJWTAssertion :: AC.Tenant -> UserKey -> IO (Maybe T.Text)
+generateJWTAssertion tenant userKey = do
+    currentTime <- P.getPOSIXTime
+    case generateAssertionClaims currentTime tenant userKey of
+        Nothing -> return Nothing
+        Just claims -> return . Just $ JWT.encodeSigned JWT.HS256 jwtSecret claims
+    where
+        jwtSecret = JWT.secret . AC.sharedSecret $ tenant
+
+-- These claims are documented here: https://developer.atlassian.com/cloud/jira/platform/oauth-2-jwt-bearer-token-authorization-grant-type/
+{-
+`iss`	String	the issuer of the claim. For example: `urn:atlassian:connect:clientid:{oauthClientId}`
+`sub`	String	The subject of the token. For example: `urn:atlassian:connect:userkey:{userkey of the user to run services on behalf of}` **NOTE:** The `userkey` is different from the `username`. For example, to get the `userkey` for username alex use the REST endpoint `/rest/api/2/user?username=alex` in JIRA or `/rest/api/user?username=alex` in Confluence.
+`tnt`	String	The instance the add-on is installed on. For example: `https://{your-instance}.atlassian.net`
+`aud`	String	The Atlassian authentication server: `https://auth.atlassian.io`
+`iat`	Long	Issue time in seconds since the epoch UTC.
+`exp`	Long	Expiry time in seconds since the epoch UTC. Must be no later that 60 seconds in the future.
+-}
+generateAssertionClaims :: P.POSIXTime -> AC.Tenant -> UserKey -> Maybe JWT.JWTClaimsSet
+generateAssertionClaims fromTime tenant userKey = do
+    oid <- AC.oauthClientId tenant
+    return JWT.JWTClaimsSet
+        { JWT.iss = JWT.stringOrURI $ "urn:atlassian:connect:clientid:" `T.append` oid
+        , JWT.sub = JWT.stringOrURI $ "urn:atlassian:connect:userkey:" `T.append` userKey
+        , JWT.aud = Left <$> JWT.stringOrURI authBaseUrl
+        , JWT.iat = JWT.numericDate fromTime
+        , JWT.exp = JWT.numericDate expiryTime
+        , JWT.nbf = Nothing
+        , JWT.jti = Nothing
+        , JWT.unregisteredClaims = M.fromList [("tnt", String . T.pack $ tenantBaseUrlString)]
+        }
+    where
+        tenantBaseUrlString = show . AC.getURI . AC.baseUrl $ tenant
+
+        expiryTime :: P.POSIXTime
+        expiryTime = fromTime + timeUnitToDiffTime expiryPeriod
+
+        -- One minute is the maximum expiry time for this token
+        expiryPeriod :: Minute
+        expiryPeriod = 1
+
+authBaseUrl :: T.Text 
+authBaseUrl = "https://auth.atlassian.io"
