I just announced the newSpring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY

1. Overview

In this second article of the series, we’ll build some simple functionality to post on Reddit from our application, via their API.

2. Necessary Security

First – let’s get the security aspect out of the way.

In order to Submit a Link to Reddit, we need to define an OAuth protected Resource with the scope of “submit“:

@Bean
public OAuth2ProtectedResourceDetails reddit() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    details.setId("reddit");
    details.setClientId(clientID);
    details.setClientSecret(clientSecret);
    details.setAccessTokenUri(accessTokenUri);
    details.setUserAuthorizationUri(userAuthorizationUri);
    details.setTokenName("oauth_token");
    details.setScope(Arrays.asList("identity", "submit"));
    details.setGrantType("authorization_code");
    return details;
}

Note that we’re also specifying the scopeidentity” because we also need access the user account information.

3. Is Captcha Needed?

Users that are new to Reddit have to fill in a Captcha in order to submit; that is before they pass a certain karma threshold within Reddit.

For these users, we first need to check if the Captcha is needed:

private String needsCaptcha() {
    String result = redditRestTemplate.getForObject(
      "https://oauth.reddit.com/api/needs_captcha.json", String.class);
    return result;
}

private String getNewCaptcha() {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity req = new HttpEntity(headers);

    Map<String, String> param = new HashMap<String, String>();
    param.put("api_type", "json");

    ResponseEntity<String> result = redditRestTemplate.postForEntity(
      "https://oauth.reddit.com/api/new_captcha", req, String.class, param);
    String[] split = result.getBody().split("""); 
    return split[split.length - 2];
}

4. The “Submit Post” Form

Next, let’s create the main form for submitting new posts to Reddit. Submitting a Link requires the following details:

  • title – the title of the article
  • url – the URL of the article
  • subreddit – the sub-reddit to submit the link to

So let’s see how we can display this simple submission page:

@RequestMapping("/post")
public String showSubmissionForm(Model model) throws JsonProcessingException, IOException {
    String needsCaptchaResult = needsCaptcha();
    if (needsCaptchaResult.equalsIgnoreCase("true")) {
        String iden = getNewCaptcha();
        model.addAttribute("iden", iden);
    }
    return "submissionForm";
}

And of course the basic submissionForm.html:

<form>
    <input name="title"/>
    <input name="url" />
    <input name="sr"/>
    <input  type="checkbox" name="sendReplies" value="true"/>

    <div th:if="${iden != null}">
        <input type="hidden" name="iden" value="${iden}"/>
        <input name="captcha"/>
        <img src="http://www.reddit.com/captcha/${iden}" alt="captcha" width="200"/>
    </div>
    <button type="submit" onclick="submitPost()">Post</button>
</form>

<script>
function submitPost(){
    var data = {};
    $('form').serializeArray().map(function(x){data[x.name] = x.value;});
    $.ajax({
        url: "api/posts",
        data: JSON.stringify(data),
        type: 'POST',
        contentType:'application/json'
    }).done(function(data) {
        if(data.length < 2){ alert(data[0]);}
        else{
            window.location.href="submissionResponse?msg="+
              data[0]+"&url="+data[1];
        }
    }).fail(function(error) { alert(error.responseText); }); 
}
</script>

Now – let’s take a look at the final step – submitting the actual link via the Reddit API.

We’ll POST a submit request to Reddit using the parameters from our submissionForm:

@Controller
@RequestMapping(value = "/api/posts")
public class RedditPostRestController {

    @Autowired
    private RedditService service;

    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public List<String> submit(@Valid @RequestBody PostDto postDto) {
        return service.submitPost(postDto);
    }
}

Here’s the actual method implementation:

public List<String> submitPost(PostDto postDto) {
    MultiValueMap<String, String> param1 = constructParams(postDto);
    JsonNode node = redditTemplate.submitPost(param1);
    return parseResponse(node);
}

private MultiValueMap<String, String> constructParams(PostDto postDto) {
    MultiValueMap<String, String> param = new LinkedMultiValueMap<String, String>();
    param.add("title", postDto.getTitle());
    param.add("sr", postDto.getSubreddit());
    param.add("url", postDto.getUrl());
    param.add("iden", postDto.getIden());
    param.add("captcha", postDto.getCaptcha());
    if (postDto.isSendReplies()) {
        param.add("sendReplies", "true");
    }

    param.add("api_type", "json");
    param.add("kind", "link");
    param.add("resubmit", "true");
    param.add("then", "comments");
    return param;
}

And the simple parsing logic, handling the response from the Reddit API:

private List<String> parseResponse(JsonNode node) {
    String result = "";
    JsonNode errorNode = node.get("json").get("errors").get(0);
    if (errorNode != null) {
        for (JsonNode child : errorNode) {
            result = result + child.toString().replaceAll("\"|null", "") + "<br>";
        }
        return Arrays.asList(result);
    } else {
        if ((node.get("json").get("data") != null) && 
            (node.get("json").get("data").get("url") != null)) {
            return Arrays.asList("Post submitted successfully", 
              node.get("json").get("data").get("url").asText());
        } else {
            return Arrays.asList("Error Occurred while parsing Response");
        }
    }
}

All of this is working with a basic DTO:

public class PostDto {
    @NotNull
    private String title;

    @NotNull
    private String url;

    @NotNull
    private String subreddit;

    private boolean sendReplies;

    private String iden;
    private String captcha;
}

Finally – the submissionResponse.html:

<html>
<body>
    <h1 th:text="${msg}">Hello</h1>
    <h1 th:if="${param.containsKey('msg')}" th:text="${param.msg[0]}">Hello</h1>
    <h2 th:if="${param.containsKey('url')}"><a th:href="${param.url[0]}">Here</a></h2>
</body>
</html>

6. Conclusion

In this quick tutorial we implemented some basic Submit to Reddit functionality – simplistic but fully functional.

In the next part of this case study, we’ll implement a Schedule Post for Later functionality into our app.

The full implementation of this tutorial can be found in the github project – this is an Eclipse based project, so it should be easy to import and run as it is.

I just announced the new Spring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY