Advertisement

Login Server - Passing the login to the Game Server

Started by November 19, 2018 10:38 AM
8 comments, last by JensB 5 years, 11 months ago

I'm conceptualizing this at the moment, so this is more a high level question than a "show me the code".

Numerous game ideas I'm throwing around at the moment requires some kind of server side logic, and as such I decided that I want a separated login / authentication server (and as I type this I realize my question might belong in For Beginners - I spent 15 years of my career doing assembly, C, C++ and C# development, but that's a while back and I never did any type of low level network work).

Now, my question is - How do I communicate the fact that a certain client is authenticated to the game server?

My original idea was that once the client is authenticated, the login server shares a tooken with the client and saves that tooken somewhere - When the client then communicates with the game server, it uses that tooken to establish the connection.

The game server then checks with the authentication server if that's a valid tooken.

However, this would make the game server dependent on the login servers, and add some load to them.

I guess my questions are:

1) is the above a reasonable way of architecting it?

2) if not, any other concepts I should consider?

3) any "ready made" solutions I could make use of, or any specific documentation etc I should read up on?

Sorry if this comes up a bit like I'm too lazy to Google it, but I poked around and I think I know a little bit too little to effectively reasearch on my own. I also asked this question on Discord last night and got a "yes" to 1, but just wanted to see if there are more options and if someone can point me at 3.

 

// Jens

There are two options:

1) The login server makes a long random token, and stores it with its authorization in a database. Player provides token to each other server connection. Each other server checks token in the same database. Main benefot: Every server has an up-to-date view of who can do what. Main drawback: database becomes a choke point. (You'd typically want a very fast in-memory database, like Redis or Memcache or something like that.)

2) The login server emits a token that consists of userid/authorization/timestamp/hash, where the hash is a HMAC(userid+authorization+timestamp+shared-secret). The player provides this token to each other server. Each other server verifies the hash (using the shared secret that all your servers know, but nobody outside) and verifies that the time is not "too far ago." Main benefit: minimal coordination needed (mainly just keeping clocks in sync and configuring the same shared secret.) Main drawback: you can't easily invalidate/change a token, because it's valid based on the timestamp fixed at issuance, and there's not central store.

Ready made solutions are available, for example in the form of Kerberos or various LDAP servers, with varying trade-offs. That being said, operating some of these systems is just as complex as operating a game server farm, so a lightweight self-made system can often be good enough. (Warning, though: you need to be aware of various attacks, such as "firesheep" session sniffing of unencrypted connections.)

enum Bool { True, False, FileNotFound };
Advertisement
1 hour ago, hplus0603 said:

2) The login server emits a token that consists of userid/authorization/timestamp/hash, where the hash is a HMAC(userid+authorization+timestamp+shared-secret). The player provides this token to each other server. Each other server verifies the hash (using the shared secret that all your servers know, but nobody outside) and verifies that the time is not "too far ago." Main benefit: minimal coordination needed (mainly just keeping clocks in sync and configuring the same shared secret.) Main drawback: you can't easily invalidate/change a token, because it's valid based on the timestamp fixed at issuance, and there's not central store.l

Thanks

sorry if this is a newbie question, but what do you mean by authorization above?

// Jens

"authentication" establishes "who am i?"

"authorization" establishes "what can I do?"

So, for example, when someone logs in to play a game, and select a character, you might issue a token that says "I can play character 238428 for player ID 5123," this token would then establish the authentication (player ID 5123) and authorization (character 238428.)

What's interesting about this is that, if you build things right, you could NOT use the same token to, say, delete the account, or change the account password / e-mail address. You'd have another section of your app where you "enter your password again" to authorize those actions. (You will see this in a number of well designed login systems.) This reduces the risk/exposure of some particular token "leaks."

Another example of "authorization" separate from "authentication" is the "log in with Facebook" and "log in with Google" buttons on web sites. These typically use a protocol called OAuth2 (or, sometimes, SAML,) to let Facebook/Google share SOME account information with the site using the buttons. Often there will be a list on the side, saying "This application can: know your email address, know your age, post fake news on your wall." Those "things" the application requests to be able to do, is the "authorization" part (what can I do) separate from the "authentication" part (who am I.)

enum Bool { True, False, FileNotFound };

Thanks for the clarifications! Very helpful.

As I started thinking about this, and sketching on a UDP based C# server to do this - To match both a game client as well as an account management website - I realized if I go for method 2 - Or really either - There is no need to go this low level for a login / account management server.

I can just implement this as a web service of some kind, rather than worrying about the UDP level networking - makes sense?

EDIT: Of course, that requires me to run a webserver on the account/login server - which shouldn't be an issue I guess.

EDIT2: If I went with method 2 above, just doing a simple webservice with a database bellow it would likely be more than enough? Thoughts on that?

EDIT3: Here is quick and dirty implementation of creating the tooken, am I getting the concepts right for that?


using System;

using System.Net;
using System.Collections;
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;

namespace AccountServer
{
    class AccountServer
    {
        public static AccountServer instance = null;

        private const String secret = "itssekrit";

        static void Main(string[] args)
        {
            instance = new AccountServer();

            WebServer ws = new WebServer(IncommingWebRequest, "http://localhost:8080/AccountServer/");
            ws.Run();
            Console.WriteLine("Account web server started. Press a key to quit.");
            Console.ReadKey();
            ws.Stop();
        }
        
        // process incomming web requests
        //  expected format: http://localhost:8080/AccountServer/server?command=login etc
        // QQQ: From a load perspective, is it a problem that this is a static? What if 100s of web requests come in at once?
        public static string IncommingWebRequest(HttpListenerRequest request)
        {
            try
            {
                String command = request.QueryString["command"];

                switch (command)
                {
                    case "login":
                        return instance.WebLogin(request.QueryString);

                    default:
                        return String.Format("Unknown Request");
                }
            }
            catch(Exception e)
            {
                return String.Format("Exception in incomming request:{0}", e.ToString());
            }
        }

        // web wrapper for login process, returns the tooken if succesfull
        //  test string: http://localhost:8080/AccountServer/server?command=login&uid=jens&pwd=terii&auth=1234
        String WebLogin(NameValueCollection qs)
        {
            String user = qs["uid"];
            String password = qs["pwd"];
            String authorization = qs["auth"];
            String tooken = "";

            if( user == null || password == null || authorization == null )
                return "Bad Parameters";

            if( Login(user, password, authorization, out tooken) )
            {
                return tooken;
            }

            return "Invalid Login";
        }

        // login
        // returns true if valid, false if not valid - outputs a tooken string as a parameter
        bool Login(String user, String password, String authorization, out String tooken)
        {
            // for now just fake a successfull login
            tooken = user + ";" + authorization + ";" + DateTime.Now.ToBinary();

            var enc = Encoding.ASCII;
            HMACSHA1 hmac = new HMACSHA1(enc.GetBytes(secret));
            hmac.Initialize();

            byte[] buffer = enc.GetBytes(tooken);
            tooken = tooken + ";" + BitConverter.ToString(hmac.ComputeHash(buffer)).Replace("-", "").ToLower();        // are the replaces needed?
            
            return true;
        }        
    }
}

 

Here is a link to the WebServer class I'm using: https://codehosting.net/blog/BlogEngine/post/Simple-C-Web-Server

 

 

// Jens

In general it is easiest to use an established framework for this type of thing, unless you already know what you are doing and the pitfalls you face.

Things that jump out at me are:

  1. It appears you are storing passwords instead of using something like bcrypt 
  2. It appears you are using non-secure endpoints
  3. It appears you are not using a random token
  4. It appears you have a complete lack of any validation
  5. It appears you are leaking more than success/failure

1. NEVER store passwords. Store the hash of passwords.  It should be impossible for an attacker to recover any passwords from a database, even if they had full copies of the database and the source code. Hopefully you've got another layer in there that is handling that, which is the reason it doesn't appear in the code.

2. Use https. There are only a tiny number of reasons to use unsecured http, and you haven't described any of those reasons in your examples.

3. Generally authentication tokens are completely random. The random value is temporarily associated with the account. Don't encode anything to do with the account or any other secrets in the token.  It should only be a series of random numbers, game servers can query the authentication servers to see if the token has permissions or not.

4. Never accept raw user data. Validate everything. Parameter lengths are proper, parameter characters are valid, etc.

5. It appears you are giving too much information on errors.  The user should only see a generic failure message or a login success message.  If you reveal other information it opens a host of other potential attacks. They can scan for email addresses that are valid based on error messages, then they can compare those against darknet password lists, for example.

Advertisement

Thanks, a lot of useful input. 

 

For clarity, the above code is just a hack. And the feedback I was looking for is on how it does the method 2 in the previous response I was given (hashing a tooken).

 

i don’t understand how random plays into that method?

also, could you point me to an established framework?

 

thanks again 

// Jens

There are two kinds of tokens:

1) completely random tokens, which serve as a key into a database of token authorization

2) completely non-random tokens, which contain the authorization themselves, plus a signature so that some other endpoint (that knows the secret) can validate the claimed authorization

Your implementation seems to generate a token of kind 2, which is fine... except the "username" and "password" and "authorization" arguments actually need to be validated. Does the user exist? Is the password correct? Can the user claim the privileges granted by "authorization"? The server, as implemented, just mints a token blindly for any user with any authorization, no matter what the given password is. So, the inner primitive ("mint a token") seems like it could work, but everything that goes on the outside needs to be implemented, too :-)

Also, you didn't yet write the function that accepts a token, and validates that it's actually cromulent. In web services, these tokens are often provided as an Authorization header:


Authorization: Bearer {token-goes-here}

enum Bool { True, False, FileNotFound };

Thanks, yep. I wanted to get the token right before I did the rest and then I’ve not had time. 

Password validation, not storing and sending clear text etc are all to be implemented ;)

// Jens

This topic is closed to new replies.

Advertisement