Steam OpenID Signature Validation
Asked Answered
R

1

6

I've been having this issue for a while now. I'm trying to add a Sign in through Steam button, which upon login, not only retrieves the user's ID, but also validates the signature. Steam uses OpenID 2.0.

I have followed the documentation here. I have followed these steps carefully, spending the better part of my day on trying to figure this out. My code is this:

let s = data['openid.signed'].split(',');
let x = Buffer.from(s.map(x => `${x}:${data['openid.' + x]}`).join('\n') + '\n', 'utf8');
let c = crypto.createHash('sha1').update(x).digest('base64');
console.log(x.toString('utf8')); // This is the key:value string
console.log(c); // This is the final result; the generated signature

Where data is the response given from the OpenID provider. Logging x (key:value pair string) gives the expected output of:

signed:signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle
op_endpoint:https://steamcommunity.com/openid/login
claimed_id:https://steamcommunity.com/openid/id/765611981[1234567]
identity:https://steamcommunity.com/openid/id/765611981[1234567]
return_to:http://127.0.0.1:8000/resolve
response_nonce:2018-12-01T17:53: [some_hash]=
assoc_handle:1234567890

However, my generated hash c does not match the given signature, openid.sig. Note that I use a \n at the end of the above key:value pair string, as that is how I interpreted the documentation.

Note. The reason why I need authentication is that I want to connect the Steam account to an account on my website, and being logged in via Steam gives you full access to your account on my website, meaning that it's of utter importance that a user cannot simply enter another users id and get access to their account (replay attack). Because of this, I need to somehow validate the signature.

I have never worked with OpenID before, so please excuse any foolish mistakes of mine. I highly recommend reading the documentation that is linked above, so that you can verify what I am doing is right.

Kinds regards,

Roseboro answered 1/12, 2018 at 18:29 Comment(4)
I only see HMAC authentication. HMAC requires a pre-established secret key and consists of more than just a hash.Twilley
You are right. I realised this after a while. However, I am unsure of what secret key to use. I do have a Steam API Key, but I am not sure if this is it's intended use. What I came up with was instead this line crypto.createHmac('sha1', 'SteamAPIKey32Chars').update(x).digest('base64'); but that still does not give the intended hash.Roseboro
It's a shame that the documentation on Steam is so poor. The nonces include colons, which according to the OpenID documentation is not an allowed character. It all boils down to Steam's implementation in the end, which I know nothing of. Just one example would be enough!Roseboro
@troffaholic did you ever solve this?Gove
A
19

Initial Request

Make your Steam login button link to

https://steamcommunity.com/openid/login?openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=https://mywebsite.com&openid.realm=https://mywebsite.com&openid.mode=checkid_setup

and replace the openid.return_to and openid.realm query string parameters.

openid.return_to: This is the URL that Steam will redirect to upon successful login with appended query string parameters.

openid.realm The URL Steam will ask the user to trust. It will appear as a message like this when the user is on the Steam login page: Sign into {openid.realm} using your Steam account. Note that {openid.realm} is not affiliated with Steam or Valve.

Handling the response

Upon successful login, Steam will redirect to a URL like

https://mywebsite.com/?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/76561198002516729&openid.identity=https://steamcommunity.com/openid/id/76561198002516729&openid.return_to=https:/%mywebsite.com&openid.response_nonce=2020-08-27T04:44:16Zs4DPZce8qc+iPCe8JgQKB0BiIDI=&openid.assoc_handle=1234567890&openid.signed=signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle&openid.sig=W0u5DRbtHE1GG0ZKXjerUZDUGmc=

To verify the user, make a call from your backend to https://steamcommunity.com/openid/login copying every query string parameter from that response with one exception: replace &openid.mode=id_res with &openid.mode=check_authentication. So the final call will be to this URL:

https://steamcommunity.com/openid/login?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/76561198002516729&openid.identity=https://steamcommunity.com/openid/id/76561198002516729&openid.return_to=https://mywebsite.com&openid.response_nonce=2020-08-28T04:44:16Zs4DPZce8qc+iPCe8JgQKB0BiIDI=&openid.assoc_handle=1234567890&openid.signed=signed,op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle&openid.sig=W0u5DRbtHE1GG0ZKXjerUZDUGmc=

Steam will return a text/plain response like this:

ns:http://specs.openid.net/auth/2.0
is_valid:true

If true the user is valid, false invalid. Note this call will only return true once and subsequent calls with the same parameters will always return false. From here, you can decide how to maintain the user being logged in (such as creating a unique cookie) and return a redirect response to something like your site's homepage, last page before they clicked the Steam login button, or user detail page, etc...

Allowed answered 29/8, 2020 at 22:53 Comment(2)
I kept hitting a Server denied check_authentication in python, this answer was helpful in finding what was wrong. This answer can test the OP with curl. +1Blasphemous
@MatthewStevenMonkan How can i get the token attached in Set-Cookie header of request in Nuxt 3? Same handling the response process is done by backend developer as described in the answer. Steam login directs to processlogin api. Processlogin verifies the token with steam in the background and takes the user's additional information, then generates a unique token for this user, and send this token in Set-Cookie header of processlogin api. The problem is i cannot get the token from set-cookie header. how can i solve this?Electroplate

© 2022 - 2024 — McMap. All rights reserved.