heddit-0.0.1: doc/auth.org
#+TITLE: Authenticating with OAuth
~heddit~ only supports authenticating via OAuth. You can use any of the applications types that can be registered on Reddit:
- [[#script][Scripts]]
- [[#web][Web applications]]
- [[#installed][Installed applications]]
- [[#app-only][Application-only]]
The first three application types take a user context. If you do not wish to have a user context, you can create an ~ApplicationOnly~ app. You must first register your application on Reddit before using ~heddit~, even for ~ApplicationOnly~ apps.
Although Reddit supports alternative OAuth flows for each application type, ~heddit~ currently only supports the default flows detailed below.
In brief, [[#script][scripts]] and [[#app-only][application-only]] apps can use ~loadClient~ and ~newClient~ to create a Reddit client. [[#web][web]] and [[#installed][installed]] apps can use ~newClient~ in which case OAuth refresh tokens will be unmanaged and will not persist after the end of your program, or ~newClientWithManager~, which will manage the tokens when provided with a ~TokenManager~.
~heddit~ will automatically refresh access tokens when they are close to expiration.
You must also provide a unique ~UserAgent~ when creating your ~Client~, as the code snippets below assume.
* Scripts
:PROPERTIES:
:CUSTOM_ID: script
:END:
*Script apps* are the simplest application to configure and use, but also require providing your Reddit username and password. Your credentials will be kept in memory in the ~Client~ that is created, so if this is not desirable, consider using a different application type. There is no callback process with script apps.
To create a script app, you need to provide your:
- *client ID*
- *client secret*
- *username*
- *password*
At the moment, ~heddit~ supports two ways of creating new ~ScriptApps~. The first is directly provide the relevant details in ~newClient~, as so:
#+begin_src haskell
main :: IO ()
main = do
c <- newClient myAuthConfig
...
where
myAuthConfig = AuthConfig myClientID myScriptApp myUA
myScriptApp = ScriptApp myClientSecret myPWFlow
myPWFlow = PasswordFlow myUsername myPassword
#+end_src
The second way is to store your credentials and other details in an ~auth.ini~ file and use ~loadClient~. See the details of [[file:../src/Network/Reddit.hs::loadClient][loadClient]] for the location and format of these files.
* Web applications
:PROPERTIES:
:CUSTOM_ID: web
:END:
*Web apps* use a ~CodeFlow~ and require configuring an OAuth callback to generate the auth tokens. The steps outlined below largely apply to [[#installed][installed apps]] as well. This flow has several benefits:
- you want to access the Reddit accounts of your users from a web or installed application
- you are using a script and do not want to store your username and password in memory
- you want to use delimited scopes with your script application (using a [[#script][script app]] with a ~PasswordFlow~ implicitly grants all scopes)
If you are not actually running a web service, you can still create a ~WebApp~ by registering the correct application on Reddit and running [[file:../examples/RefreshTokens.hs][the RefreshTokens example program]] to get the initial token.
The first step in completing a ~CodeFlow~ is to provide a valid redirect URI when creating your application. This will be used to generate a URL that your users can visit to authorize your application, resulting in a single-use code that will be used to create your ~Client~, e.g.:
#+begin_src haskell
getURL :: MonadIO m => URL -> ClientID -> m URL
getURL myRedirectURL myClientID = do
-- The state should be unique to each user. You can confirm that it matches when you
-- extract the params from Reddit's response
state <- T.pack . show <$> randomRIO @Int (0, 65000)
pure $ getAuthURL myRedirectURI Permanent [ Read, Save, Vote, Accounts ] clientID state
#+end_src
See [[file:../src/Network/Reddit.hs::getAuthURL][getAuthURL]] for details.
Once you have your ~CodeFlow~ using the code and your redirect URL, you can use this to create a ~Client~. If you use ~newClient~, ~heddit~ will fetch the initial refresh token and exchange it for an access token. The tokens will be stored in the ~Client~ that you create, but if you do not store them, your users will need to complete the authentication flow again.
#+begin_src haskell
myNewClient :: (MonadUnliftIO m, MonadCatch m) => Code -> m Client
myNewClient code = newClient authConfig
where
authConfig = AuthConfig myClientID app myUserAgent
app = WebApp myClientSecret codeFlow
codeFlow = CodeFlow redirectURI code
#+end_src
An alternative is to use ~getAuthURL~ to obtain the code, then get the refresh token using a similar method to what is demonstrated in ~examples/RefreshTokens.hs~. After getting the refresh token, you can use [[file:../src/Network/Reddit.hs::newClientWithManager][newClientWithManager]] and provide ~heddit~ with a record of monadic actions that will load and store your tokens. See the [[file:../src/Network/Reddit.hs::fileTokenManager][fileTokenManager]] for a simple example.
* Installed applications
:PROPERTIES:
:CUSTOM_ID: installed
:END:
*Installed apps* are similar to [[#web][web]], but do not take a secret and also follow the ~CodeFlow~.
* Application-only
:PROPERTIES:
:CUSTOM_ID: app-only
:END:
If you do not wish to use your Reddit ~Client~ with a user context, create an ~ApplicationOnly~ app. The process is indentical to [[#script][scripts]], but you do not provide a ~PasswordFlow~ to create the ~Client~. ~ApplicationOnly~ apps may also use ~loadClient~ with an ~auth.ini~ file. NB: many endpoints will not work, so be prepared for exceptions when working within this context.