OAuth2

reddit uses OAuth2 to control access to its API. JRAW provides some utilities to make this (generally painful) process as painless as possible. If you'd like to know what JRAW is actually doing, make sure to read this wiki page!

Types of OAuth2 Apps

reddit provides three types of OAuth2 apps:

  • Web app: Runs as part of a web service on a server you control. Can keep a secret.
  • Installed app: Runs on devices you don't control, such as the user's mobile phone. Cannot keep a secret, and therefore, does not receive one.
  • Script app: Runs on hardware you control, such as your own laptop or server. Can keep a secret. Only has access to your account.

If you're not sure which app type is best for you, check out this page.

Normally, these apps require a user to allow the app access to their account. This isn't always necessary, however. An installed or web app can utilize "application-only" or "userless" mode, which allows it access to the API without a user.

This makes for a total of five different AuthMethods.

To create a reddit OAuth2 app, see here.

Automatic Authentication

Automatic authentication is used for script and userless apps and can be done in one line of code!

// Use Credentails.script, Credentials.userless, or Credentials.userlessApp
RedditClient reddit = OAuthHelper.automatic(networkAdapter, scriptOrUserlessCredentials);

Interactive Authentication

Interactive authentication is used for installed and web apps (e.g. Android apps) and is a little more complex. The general process goes something like this:

  1. Generate an authorization URL for the user.
  2. Direct the user to this URL. This will prompt them to either allow or deny your app access to their account.
  3. After the user decides, reddit will redirect them to the OAuth2 app's redirect_uri with some extra data in the query.
  4. Verify this data and request the access token.

JRAW handles steps 1, 3, and 4 for you. It's your job to show the user the authorization URL. Here's a quick example of what this process might look like:

NetworkAdapter networkAdapter = new OkHttpNetworkAdapter(userAgent);
final StatefulAuthHelper helper =
    OAuthHelper.interactive(networkAdapter, installedOrWebCredentials);

// 1. Generate the authorization URL. This will request a refresh token (1st parameter),
// use the mobile site (2nd parameter), and request the "read" and "vote" scopes (all
// other parameters).
String authUrl = helper.getAuthorizationUrl(true, true, "read", "vote");

// This class mirrors the Android WebView API fairly closely
final MockWebView browser = new MockWebView();

// Listen for pages starting to load
browser.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageStarted(String url) {
        // 3. Listen for the final redirect URL.
        if (helper.isFinalRedirectUrl(url)) {
            // No need to continue loading, we've already got all the required information
            browser.stopLoading();

            // 4. Verify the data and request our tokens. Note that onUserChallenge
            // performs an HTTP request so Android apps will have to use an AsyncTask
            // or Observable.
            RedditClient reddit = helper.onUserChallenge(url);

            // We now have access to an authenticated RedditClient! Test it out:
            Account me = reddit.me().query().getAccount();
        }
    }
});

// 2. Show the user the authorization URL. This has to go after setWebViewClient because
// otherwise it'll open up the default browser instead of loading data in the WebView.
browser.loadUrl(authUrl);

If your app is using userless mode, you can use automatic authentication instead!

See the Android page for more.

Renewing an Access Token

Access tokens are what enables us to send requests to oauth.reddit.com successfully. These expire one hour after they were requested. JRAW is capable of refreshing access tokens automatically if it has the right data.

Automatic authentication

If you're using automatic authentication, you don't have to do anything! JRAW automatically requests new data the same way it was done originally.

Interactive authentication

In order for JRAW to get a new access token, it must use a refresh token. A refresh token is a special string unique to your app that allows the client to request new access tokens. Refresh tokens don't expire, so once you have one, you can requests new access tokens until the refresh token is manually revoked.

To make sure JRAW has access to a refresh token, make sure you pass true for requestRefreshToken in StatefulAuthHelper.getAuthorizationUrl:

boolean requestRefreshToken = true;
boolean useMobileSite = true;
String[] scopes = { "account", "edit", "flair" };
String authUrl =
    statefulAuthHelper.getAuthorizationUrl(requestRefreshToken, useMobileSite, scopes);

Manually renewing

If for some reason you don't want JRAW to automatically request new data for you, you can disable this feature and refresh manually like this:

// Disable auto renew
redditClient.setAutoRenew(false);

// Manually renew the access token
AuthManager authManager = redditClient.getAuthManager();
if (authManager.canRenew()) {
    authManager.renew();
}

Revoking an Access or Refresh Token

As you may have learned from before, access tokens expire after one hour and refresh tokens don't expire. If your app doesn't need a token, it's generally a good idea to revoke it. In this case, when we revoke a token, we're really telling reddit that we're done with this token and that it should revoke the token's ability to interact with the API.

// Revoke the access token
redditClient.getAuthManager().revokeAccessToken();

// Revoke the refresh token
redditClient.getAuthManager().revokeRefreshToken();

You're not required to do this, but it reduces the chance that an attacker could use an old access or refresh token to gain access to a user's account.

TokenStores

Writing an installed app can be much more complex task than a small script. How do you keep track of access and refresh tokens? How do you handle different users? How do you prevent an additional access token request when there's already an unexpired token floating out there?

JRAW attempts to solve this problem with TokenStores. A TokenStore stores, retrieves, and deletes access and refresh tokens for different users. Usually this data should be saved somewhere more permanent (like a file).

TokenStores can be given as a parameter to OAuthHelper methods:

// Automatic authentication
RedditClient redditClient =
    OAuthHelper.automatic(networkAdapter, credentials, tokenStore);

// Or interactive authentication
StatefulAuthHelper authHelper =
    OAuthHelper.interactive(networkAdapter, credentials, tokenStore);

Whenever the RedditClient refreshes its access token, it will also notify its TokenStore!

TokenStores aren't incredibly useful by themselves. For the best results, use an AccountHelper as well.

AccountHelper

Creating a reddit app for Android or the web can be quite daunting. Developers have to manage different users, switching between users and into/out of userless mode, and storing and organizing all of this data. JRAW tries to make this as easy as possible.

As an additional level of abstraction for Android developers, JRAW includes the AccountHelper class. Think of AccountHelper like a factory for RedditClients. You give the factory some basic information (a NetworkAdapter, OAuth2 app credentials, etc.) and it produces authenticated RedditClients for you.

Here's a quick tour:

AccountHelper helper =
    new AccountHelper(networkAdapter, credentials, tokenStore, deviceUuid);

// When we first load up the app, let's use userless mode so the user can still read the
// front page, etc.
RedditClient reddit = helper.switchToUserless();
// AccountHelper always maintains a reference to the active RedditClient
assert helper.getReddit() == reddit;

// The user has decided to log in. Let's see if we already have a refresh token or an
// unexpired access token on hand
String username = "foo"; // for the sake of example

try {
    // We could avoid try/catch by using trySwitchToUser, which returns null instead of
    // throwing an Exception.
    helper.switchToUser(username);
} catch (IllegalStateException e) {
    // There wasn't any data we could use, we have to have the user allow our app access
    // to their account
    StatefulAuthHelper statefulAuthHelper = helper.switchToNewUser();

    // (do the auth process as described in the "Interactive Authentication" section)

    String finalUrl = "bar"; // for the sake of example
    statefulAuthHelper.onUserChallenge(finalUrl);
}

reddit = helper.getReddit();

// We should have some data stored now!
assert tokenStore.fetchLatest(username) != null;
assert reddit.getAuthManager().getAccessToken()
    .equals(tokenStore.fetchLatest(username).getAccessToken());

// Repeat this process whenever we have a new user

// Now let's say the user logs out:
reddit = helper.switchToUserless();

results matching ""

    No results matching ""