How to make a login with Steam button with PHP (OpenID)
7 min read

How to make a login with Steam button with PHP (OpenID)

How to make a login with Steam button with PHP (OpenID)

Video Tutorial

Source Code

GitHub - mbouldo/login-with-steam-php: A simple login with Steam starter project using open ID and PHP
A simple login with Steam starter project using open ID and PHP - GitHub - mbouldo/login-with-steam-php: A simple login with Steam starter project using open ID and PHP

Getting Started

Starter HTML & File Structure

Similar to my "Login with Discord PHP" guide, we will have a basic landing page with a button to navigate to the auth page (steam), and then be redirected back to a login protected dashboard page.

Therefore, index.php handles the homepage. dashboard.php is the login protected dashboard. init-openId.php will build parameters and redirect the user to steam.com, and finally, process-openId.php will listen for steam's return data on the authentication.

The rest of the files uses such as error.php, and logout.php are not essential, but help with error handling and destroying the user's session after they have successfully logged in.

Finally, a tailwind generated output.css is used for basic styling and can be found in the source code repository.

Starter index.php

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="./styles/output.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body class="bg-blue-400">
    <div class="flex items-center justify-center h-screen bg-slate-900 text-white">
        <a href="init-openId.php" class="bg-steam-lightGray  text-xl px-5 py-3 rounded-md font-bold flex items-center space-x-4 hover:bg-gray-600 transition duration-75">
            <i class="fa-brands fa-steam text-2xl"></i>
            <span>Login with Steam</span>
        </a>
    </div>
</body>
</html>

Starter dashboard.php

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="./styles/output.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
    <div class="flex items-center justify-center h-screen bg-steam-lightGray text-white flex-col">
        <div class="text-2xl">Welcome to the dashboard,</div>
        <div class="text-4xl mt-3 flex items-center font-medium"><img src='{{avatar_src}}' class="rounded-full w-12 h-12 mr-3"/>{{username}}</div>
        <a href="logout.php" class="text-sm mt-5">Logout</a>
    </div>

</body>
</html>

Making dashboard.php login protected

In order to not allow a random user to directly access this URL, we will start a session at the top of the page and see if the session variable: logged_in is equal to true. Currently, it will be undefined. But we will see it later in the application.

Add this to the top of the dashboard.php page:

session_start();
if(!$_SESSION['logged_in']){
    header("location: error.php");
    exit();
}

error.php will be text you want to show the user to notify them they are not logged in.

Redirecting the user for the first time

In my opinion, it's a good idea to have a separate redirect page to initiate external authorization. This wasn't really obvious with discord which just used a hardcoded URL; here, we will need to build more complex parameters.

Building open Id parameters:

We need to construct the following paramters which will be passed as a GET query to the intial URL. It will tell steam what protocol we will use, and how to redirect our user back to our server.

$login_url_params = [
    'openid.ns'         => 'http://specs.openid.net/auth/2.0',
    'openid.mode'       => 'checkid_setup',
    'openid.return_to'  => 'http://localhost/login-with-steam-yt/process-openId.php',
    'openid.realm'      => (!empty($_SERVER['HTTPS']) ? 'https' : 'http').'://'.$_SERVER['HTTP_HOST'],
    'openid.identity'   => 'http://specs.openid.net/auth/2.0/identifier_select',
    'openid.claimed_id' => 'http://specs.openid.net/auth/2.0/identifier_select',
];

Constructing our URL from the parameters, and redirecting

$steam_login_url = 'https://steamcommunity.com/openid/login'.'?'.http_build_query($login_url_params, '', '&');

header("location: $steam_login_url");
exit();

If you did set up your URL correctly, the steam page should look like this:

And once logged in, will look like this:

What happens after the user "logs in"

Steam will validate the authentication internally, and then communicate to the OpenID API to compose a request to send back to you, and your server. (We specified the "our server endpoint" in those initial parameters).

So, we need a file to receive that incoming request, and process the new data we have.

We are expecting information some kind of token to reference who in fact was validated. Steam will not directly say "Jimmy" was validated, because you could spoof this information.

The token we receive is in the form of a long set of $_GET parameters, looking a little like this:

Array
(
    [openid_ns] => http://specs.openid.net/auth/2.0
    [openid_mode] => id_res
    [openid_op_endpoint] => https://steamcommunity.com/openid/login
    [openid_claimed_id] => https://steamcommunity.com/openid/id/76561198068485408
    [openid_identity] => https://steamcommunity.com/openid/id/76561198068485408
    [openid_return_to] => http://localhost/login-with-steam-yt/process-openId.php
    [openid_response_nonce] => 2022-05-20T02:22:02ZL9LZ+XnIhRJAYbiXMNGhaWfAF8c=
    [openid_assoc_handle] => 1234567890
    [openid_signed] => signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle
    [openid_sig] => Td6XOTHOCYU22oABLocdFvgsGBk=
)

We will take this data, and pass it to steam's open id handler, to see if the request is valid, and see who was validated (steam id).

Constructing parameters to send to steam open id

Initially, we can hardcode some parameters, and then we can

    $params = [
        'openid.assoc_handle' => $_GET['openid_assoc_handle'],
        'openid.signed'       => $_GET['openid_signed'],
        'openid.sig'          => $_GET['openid_sig'],
        'openid.ns'           => 'http://specs.openid.net/auth/2.0',
        'openid.mode'         => 'check_authentication',
    ];

dynamically use the $_GET['openid_signed'] parameter to construct more parameters like so

    $signed = explode(',', $_GET['openid_signed']);
    
    foreach ($signed as $item) {
        $val = $_GET['openid_'.str_replace('.', '_', $item)];
        $params['openid.'.$item] = stripslashes($val);
    }

Your parameters should look like this:

Array
(
    [openid.assoc_handle] => 1234567890
    [openid.signed] => signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle
    [openid.sig] => Td6XOTHOCYU22oABLocdFvgsGBk=
    [openid.ns] => http://specs.openid.net/auth/2.0
    [openid.mode] => check_authentication
    [openid.op_endpoint] => https://steamcommunity.com/openid/login
    [openid.claimed_id] => https://steamcommunity.com/openid/id/76561198068485408
    [openid.identity] => https://steamcommunity.com/openid/id/76561198068485408
    [openid.return_to] => http://localhost/login-with-steam-yt/process-openId.php
    [openid.response_nonce] => 2022-05-20T02:22:02ZL9LZ+XnIhRJAYbiXMNGhaWfAF8c=
)

Constructing headers for steam's open ID endpoint

Similar to cURL, we will construct headers to hit steam's endpoint with our open id information.

$data = http_build_query($params);
//data prep
$context = stream_context_create([
    'http' => [
        'method' => 'POST',
        'header' => "Accept-language: en\r\n".
        "Content-type: application/x-www-form-urlencoded\r\n".
        'Content-Length: '.strlen($data)."\r\n",
        'content' => $data,
    ],
]);

//get the data
$result = file_get_contents('https://steamcommunity.com/openid/login', false, $context);

Checking if $result is valid

Use regular expression to check if the string contains the correct "valid" syntax, if its valid set the $steamId

if(preg_match("#is_valid\s*:\s*true#i", $result)){
    preg_match('#^https://steamcommunity.com/openid/id/([0-9]{17,25})#', $_GET['openid_claimed_id'], $matches);
    $steamID64 = is_numeric($matches[1]) ? $matches[1] : 0;
    echo 'request has been validated by open id, returning the client id (steam id) of: ' . $steamID64;    

}else{
    echo 'error: unable to validate your request';
    exit();
}

Using the steam id to get the user data

You will need a steam API key for this step. To obtain your key, visit this link.

We use a simple file_get_contents() to get the data on the user, with our API key in the URL.

$response = file_get_contents('https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='.$steam_api_key.'&steamids='.$steamID64);
$response = json_decode($response,true);

Example return from steam API

Array
(
    [response] => Array
        (
            [players] => Array
                (
                    [0] => Array
                        (
                            [steamid] => 76561198068485408
                            [communityvisibilitystate] => 3
                            [profilestate] => 1
                            [personaname] => Exit
                            [commentpermission] => 1
                            [profileurl] => https://steamcommunity.com/profiles/76561198068485408/
                            [avatar] => https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/3c/3...
                            [avatarmedium] => https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/3c/3c...
                            [avatarfull] => https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/3c/3c2...
                            [avatarhash] => 3c2ae1c9d9e96429c2a274728b55e71c7e745a40
                            [lastlogoff] => 1652935474
                            [personastate] => 0
                            [realname] => Martin B
                            [primaryclanid] => 103582791441483516
                            [timecreated] => 1343770109
                            [personastateflags] => 0
                            [loccountrycode] => US
                            [locstatecode] => 13
                            [loccityid] => 40043
                        )

                )

        )

Since it's an array of users, we will get the relavent data from the response, and set our session variables.

$userData = $response['response']['players'][0];
$_SESSION['logged_in'] = true;
$_SESSION['userData'] = [
    'steam_id'=>$userData['steamid'],
    'name'=>$userData['personaname'],
    'avatar'=>$userData['avatarmedium'],
];

From there, we can redirect the user using a header.

$redirect_url = "dashboard.php";
header("Location: $redirect_url"); 
exit();

Finally, on the dashboard, we append getting the user data from our new session.

<?php
session_start();
if(!$_SESSION['logged_in']){
    header("location: error.php");
    exit();
}

$username = $_SESSION['userData']['name'];
$avatar = $_SESSION['userData']['avatar'];

?>
<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="./styles/output.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>
<body>
    <div class="flex items-center justify-center h-screen bg-steam-lightGray text-white flex-col">
        <div class="text-2xl">Welcome to the dashboard,</div>
        <div class="text-4xl mt-3 flex items-center font-medium"><img src='<?php echo $avatar;?>' class="rounded-full w-12 h-12 mr-3"/><?php echo $username;?></div>
        <a href="logout.php" class="text-sm mt-5">Logout</a>
    </div>

</body>
</html>