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:
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:
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:
<cfset steamLoginUrl = "https://steamcommunity.com/openid/login?">
<cfset steamLoginUrl = "#steamLoginUrl#openid.ns=#UrlEncodedFormat("http://specs.openid.net/auth/2.0")#">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.mode=checkid_setup">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.return_to=#UrlEncodedFormat("MyDomain/authCallback.cfm")#">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.realm=#UrlEncodedFormat("MyDomain")#">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.ns.sreg=#UrlEncodedFormat("http://openid.net/extensions/sreg/1.1")#">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.claimed_id=#UrlEncodedFormat("http://specs.openid.net/auth/2.0/identifier_select")#">
<cfset steamLoginUrl = "#steamLoginUrl#&openid.identity=#UrlEncodedFormat("http://specs.openid.net/auth/2.0/identifier_select")#">
<a href="#steamLoginUrl#">
<img src="imgs/steamLogin.png">
</a>
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.
<cfset checkUrl = cgi.request_url>
<cfset checkUrl = Replace(checkUrl, "MyUrl", "https://steamcommunity.com/openid/login")>
<cfset checkUrl = Replace(checkUrl, "&openid.mode=id_res", "&openid.mode=check_authentication")>
<cfhttp url="#checkUrl#" />
<cfif cfhttp.fileContent contains "is_valid:true">
Do stuff...
<cfelse>
Do other stuff...
</cfif>
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.