Steam Authentication with Steam4J-Auth

Steam’s OpenID-based authentication enables site visitors to login using their steam credentials without your site having to store or manage the credentials directly thereby reducing the amount of sensitive data that could be leaked in the event of a data breach.

Steam authorisation is based on the OpenID spec, as such when implemented properly should provide sufficient confidence that the user is who they say they are.

This article explains how to authenticate a steam account using the Steam4J-Auth module from the 3rd party Steam4J library.

The workflow

Let’s look at the workflow from the user’s perspective using list.tf as an example.

User visiting list.tf decides they want to make an edit to their wishlist, before they can do this, they must first login, so the user clicks on the login button to sign in. The user is redirected to steam community.com where they are asked to provide their username and password, (and their 2 factor authentication code if necessary), if they successfully login, they are redirected back to list.tf and the user is now able to edit their wishlist.

The workflow is relatively simple from the user’s perspective and probably explains why OpenId and similar authentication systems have gained so much traction.

Behind the scenes

This is what needs to happen behind the scenes in order to authenticate a user:

  • The website must be secured by https.
  • User clicks a login button which links to the steamcommunity.com openId site.
  • Steam examines the URL query parameters noting the openId.mode is set to ‘check_authorisation’, together with a redirect parameter which should be an endpoint on list.tf
  • The user enters their credentials into steamcommunity.com
  • Steam validates these credentials, and then redirects the user to the list.tf endpoint which was specified in the initial request.
  • In order to validate the user, list.tf examines the query parameters provided by steam, in particular the ‘claimedId’ and the signature.
  • list.tf server (never the client side) calls the steamcommunity.com endpoint back, passing the given request parameters and the signature.
  • steamcommunity.com checks if the signature is valid for the set of parameters provided.
  • steamcommunity.com also handles nonce expiration, so the same signature + claimedId pair cannot be replayed.

In summary, steamcommunity.com provides a signed set of claims to the client, the client submits these claims to the list.tf backend, and the list.tf backend verifies these claims with steamcommunity.com, it is the up to list.tf to store the user in a signed/encrypted cookie and then check the entitlements of that user against it’s own database.

Note that you absolutely must make the call to steamcommunity.com to verify the claim provided by the client, since without this signature based check, the claimedId is meaningless (i.e. anybody can change the claimedId).

Steam4J-Auth

The steam4j library provides a module called steam4j-auth which can help us implement authorisation in our applications.

In particular, the SteamAuth class provides several useful helper methods for implementing the above workflow.

SteamAuth is thread safe so a spring managed singleton used across your application is fine, to construct simply provide the ‘realm’ (the domain of your site) and ‘redirect’ url for example:

SteamAuth sa = new SteamAuth("https://list.tf/", "https://list.tf/api/openIDCallback");

sa.getLoginUrl() returns a string representation of the URL which the login button should link to. It contains the realm, and redirect url, as well as boiler plate parameters required. You can of course expose this link via an endpoint.

sa.authenticate(HttpServletRequest r) examines the given HttpServletRequest for the claimedId and the signature, verifies this with steam, and returns an Authorisation result which states if the login is successful, you can then based on this issue a signed cookie containing this information. Assuming you are using Spring’s REST related annotations, this method is useful since you can pass the HttpServletRequest from your endpoint directly to the authenticate method.

Example usage

Finally, here is an illustration of what a Spring rest endpoint might look like:

@RestController
@RequestMapping("/api”)
public class LoginController {
    private final SteamAuth steamAuth;

    //this method can be called by your javascript client to figure out where to redirect 
    //the user when the login button is clicked
    @RequestMapping("/getLoginUrl)
    public @ResponseBody JsonNode generateSteamUrl(HttpServletRequest request) {
        logger.info("Logging in");
        try {
            return new TextNode(steamAuth.getLoginUrl());
        } catch (Exception e) {
            throw new SteamAPIDown();
        }
    }

    //this method gets called when steamcommunity.com redirects to the site on successful login
    //you must call steamAuth.authenticate(request) in order to verify the claimedId.
    //In this method we don’t show you how to set a signed cookie, only where it should be done
    @RequestMapping("/openIDCallback")
    public @ResponseBody boolean openIDCallback(HttpServletRequest request, HttpServletResponse response) {
        AuthorizationResult result = steamAuth.authenticate(request);
        if (result.isSuccess()) {
            //perform signed cookie set here
            logger.info("{} logged in ", result.getUserId());
            return true;
        } else {
            return false;
        }
    }

    //SteamAuth instance provided via dependency injection
    public SessionController(SteamAuth steamAuth) {
        this.steamAuth = steamAuth;
    }

}