Search code examples
swiftauthenticationopenidsteam

Steam OpenID auth in Swift: only passes every other attempt


Solution:

As a user below stated, I had to escape/encode the + (%2B) symbol in the nonce and sig URL elements. First I edited both elements as the URLComponent object. In that case, the following conversion to a URL object would replace the % in the sig by itself again, making it %252B.

I resolved that problem by converting the URL to a plain string, editing that and converting it back to a URL, rather than editing the URLComponent elements.


I'm working on an app with Swift to accompany a project I'm on. We want to handle the login process via Steam OpenID. On the website (built with Angular) it works flawlessly, but I'm having some troubles on the app part.

This is my problem:

Our backend doesn't provide me a token on every attempt.

It seems to be rather random when it passes and when it fails, but in total it's been about every other attempt.

I suspected the OpenID library in our backend to be the fault, so I did multiple tries on Steam's endpoint by myself, but the behavior is exactly the same.

This is my code:

This is called on login button tap:

var session: ASWebAuthenticationSession?

func signIn() {
    
    let scheme = "myauth"
    
    let authUrl = URL(string: "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/appauth
        &openid.realm=https://mywebsite.com
        &openid.mode=checkid_setup")!
    
    self.session = ASWebAuthenticationSession.init(url: authUrl, callbackURLScheme: scheme, completionHandler: { callbackURL, error in
        guard error == nil, let successURL = callbackURL else {
            print("Error: ASWebAuthSession returned with: \(error!)")
            return
        }
        var queryItems = URLComponents(string: successURL.absoluteString)!.queryItems!
        
        HttpService.getSteamResponse(queryItems: queryItems)
    })
    
    session?.presentationContextProvider = self
    session?.prefersEphemeralWebBrowserSession = true
    self.session?.start()
}

This is the referenced function:

static func getSteamResponse(queryItems: [URLQueryItem]) {
    
    var mutatedItems = queryItems
    if var item = queryItems.enumerated().first(where: {$0.element.name == "openid.mode"}) {
        item.element.value = "check_authentication"
        mutatedItems[item.offset] = item.element
    } else {
       print("Error: Steam check has no item 'openid.mode'")
    }
    
    var urlComps = URLComponents(string: "https://steamcommunity.com/openid/login")!
    urlComps.queryItems = mutatedItems
    let url = urlComps.url!
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        
        guard let data = data else {
            debugPrint("Error: Steam check responded with: \(String(describing: error))")
            return
        }
        
        if let httpResponse = response as? HTTPURLResponse{
            print("Info: Steam check responsed with: \(httpResponse.statusCode)")
        }
        
        print("Info: Steam check data: \(String(decoding: data, as: UTF8.self))")
    }.resume()
}

This includes some additional check that might not be necessary and it might not be very optimized at some points (I'm still learning Swift), but all in all it should be working (and does... sometimes)

Further information:

Note that I replaced the actual links to our project with placeholders.

As I understand it, the ASWebAuthenticationSession callback does only need an URL scheme, which I replaced with myauth. Since I noticed Steam (or OpenID in general?) not liking custom URL schemes, I instead use https://mywebsite.com/appauth, which redirects with 301 to something like myauth://foobar.com with all parameters, so I can receive them in the app (I guess this would work via Universal links as well, but I don't have a paid dev account yet).

Now when I run this code, I can't get a consistent response. It's always a 200 with ns:http://specs.openid.net/auth/2.0 and seemingly random is_valid:false or is_valid:true.

I ran this multiple times, tried redeploying the app in-between and even erasing the simulator device, but I can't get consistent results.

Unfortunately I can only try so many times, because Steam will block the login from my network after about 15 logins.

On my last trial I received false, false, true, false, true.

My guess would be there to be some issues with concurrency that I can't notice.

I appreciate any help on this topic!


Solution

  • I encountered the same thing in Golang. Turned out, I didn't escape the nonce and sig string, and it was modified in GET parameter in the request