I'm writing a game related website that requires me to know users' Steam IDs, so it seems like the best approach is to just use a "Login with Steam" button that I see on other websites to handle my user authentication. I thought this would be pretty simple, but am having a lot of trouble getting anywhere with it.
I signed up for a Steam API key, and an old forum post directed me to docs at https://developer.valvesoftware.com/wiki/Steam_Web_API#GetPlayerSummaries_.28v0001.29 that unfortunately don't really describe the authentication process or how to make the calls. Unfortunately, I can't seem to find much other information online at all about how to do this either, at least nothing straightforward.
Perhaps I'm misunderstanding the flow, but I expected that the user will:
- See a link on my page inviting them to login with Steam
- Click said link and go to another page on my server that uses my API key to make an API call to Steam and get a URL to a Steam page that I would redirect the user to
- The user will go to the Steam page, give permission to share user information with my website, then get redirected to a callback page on my site with data about their user (or a token used to get data)
Thus I was expecting the docs to show a set of example Postman API calls necessary for this process. However, I don't see anything like that. So I think my understanding of the flow may be incorrect.
What is the correct way to implement a "Login with Steam" button on a website.
EDIT/UPDATE
With a little help from Chat GPT, I have made some progress.
I then structured a link from my website to Steam that looks like this:
https://steamcommunity.com/openid/login?openid.ns=http%3A%2F%2Fspecs%2Eopenid%2Enet%2Fauth%2F2%2E0&openid.mode=checkid_setup&openid.return_to=https%3A%2F%2FmySite%2Ecom%2FauthCallback%2Ecfm?serverName=shadygrove&openid.realm=https%3A%2F%2FmySite%2Ecom&openid.ns.sreg=http%3A%2F%2Fopenid%2Enet%2Fextensions%2Fsreg%2F1%2E1&openid.claimed_id=http%3A%2F%2Fspecs%2Eopenid%2Enet%2Fauth%2F2%2E0%2Fidentifier%5Fselect&openid.identity=http%3A%2F%2Fspecs%2Eopenid%2Enet%2Fauth%2F2%2E0%2Fidentifier%5Fselect
This allows login and successfully returns to my callback page. I then tried to verify the digital signature using this code:
<cfset openid_response = "openid.ns=#url.openid.ns#&openid.mode=#url.openid.mode#&openid.op_endpoint=#url.openid.op_endpoint#&openid.claimed_id=#url.openid.claimed_id#&openid.identity=#url.openid.identity#&openid.return_to=#url.openid.return_to#&openid.response_nonce=#url.openid.response_nonce#&openid.assoc_handle=#url.openid.assoc_handle#&openid.signed=#url.openid.signed#&openid.sig=#url.openid.sig#">
<!-- Calculate the expected signature -->
<cfset expected_signature = hmac(
openid_response,
application.steamKey,
"HmacSHA1",
"UTF-8"
)>
<!-- Compare the calculated signature with the received one -->
<cfif expected_signature eq url.openid.sig>
<cfoutput>Authentication is valid.</cfoutput>
<cfelse>
<cfoutput>Authentication is NOT valid.</cfoutput>
</cfif>
This always comes up as invalid. I tried about 10 other ways of constructing "openid_response", but it always comes back invalid. Some of the things I tried:
- Using the entire query string
- Using just the fields listed in openid.signed
- Using all openid fields in the order they appear in the url
Unfortunately, the only other documentation I've found (https://partner.steamgames.com/doc/features/auth#website) gives little detail, so I'm not even sure I'm using the right algorithm.
I have finally gotten this to work, with no help from the documentation.
My login button is generated using this code:
After login I receive a signature that I'm supposed to be able to validate using the API key I was given and the hmac() function. I've tried a hundred different ways of constructing the string I'm supposed to run through that, from using a query string to using a new line (trying "\n", Chr(10), Chr(13), etc) delimited list of values to a list of fields and values to a list of fields equals values; nothing works. And I can't find any clear documentation online of how to do it.
However, I found this answer which suggests a different validation method: Steam OpenID Signature Validation
Following the instructions there I whipped up this little nugget of code that worked on the first try, which was nice after banging my head against the wall for hours on the signature approach.
This appears to work. However, I'd still love to know how to actually get the server side signature validation to work in cfml on Railo.