Spring Top

Get started with Spring 5 and Spring Boot 2, through the reference Learn Spring course:

>> LEARN SPRING
Java Automation Job Top
We’re looking for a Backend Java/Spring Developer with Integration Experience: Read More

It’s just plain hard to get true, real-time visibility into a running auth flow.

Parts of the process can be completely hidden from us; if the complete authorization process requires a redirect from a remote OAuth production server, then every debugging effort must go through the production server.

It’s practically unfeasible to debug this locally. There’s no way to reproduce the exact state and no way to inspect what is actually happening under the hood. Not ideal.

Knowing these types of challenges, we built Lightrun - a real-time production debugging tool - to allow you to understand complicated flows with code-level information. Add logs, take snapshots (virtual breakpoints), and instrument metrics without a remote debugger, without stopping the running service, and, most importantly - in real-time and without side effects.

Learn more with this 5-minute tutorial focused on debugging these kinds of scenarios using Lightrun:

>> Debugging Authentication and Authorization Using Lightrun

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>

5. Submit a Link to Reddit

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.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Security bottom

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

>> CHECK OUT THE COURSE
REST bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course :

>> CHECK OUT THE COURSE
REST footer banner
Comments are closed on this article!