DEV Community

Ian
Ian

Posted on

Building an Idle game Part 3 - Authentication

Welcome to my series on how to go about building an Idle game. This third episode focuses on authentication both in Vue and Node.

Authentication is a fun subject, there are a million ways to authenticate a user from the more basic user / password combination, to OAuth, OpenID and others. For our game we will be using KeyCloak, it is an "Open source Identity and Access Management" application. For a lot of apps this may be overboard, but our use case requires it, so we will be taking advantage of it.

Why not use just a user/pass combo?

This is a great question and originally we did do this. However we have several applications that require a central authentication portal. Keycloak provides this, but not only this it also allows for very easy integration of social logins, access control and more.

What if I don't want to use KeyCloak?

That is fine. As we develop the authentication you will notice we use JWT tokens, so most of the code can be easily changed to remove the need for KeyCloak.

Getting started

Make sure you have KeyCloak running before you start, there is a standalone version or a docker image, by default it runs on port 8080 and on http://localhost/auth.

Environment Variables

The first new bit of tech we are going to go over is environment variables. Almost all languages have this concept to some degree - even frontend frameworks like React. The idea is that this .env file is specific to the user/server and is not shared, it is also ignored in git to prevent commiting the file. This file will contain all the applications secrets. For our app we use 4 variables

AUTH_REALM=divinity-world
AUTH_URL=http://localhost:8080/auth
AUTH_CLIENT_ID=game
AUTH_REDIRECT_URI=https://localhost:8081/play
Enter fullscreen mode Exit fullscreen mode

The AUTH_REALM is the realm you created in KeyCloak, the URL being the url to auth, the AUTH_CLIENT_ID is the client id, by default Keycloak creates an account client and a few others, I created my own game. And lastly the AUTH_REDIRECT_URI is where you get redirected after authentication.

JWKS and JWT's

There are 2 parts to authentication on the server side, the first part being the need to get the signing key from the server and the second being verification of the token, for this we use two libraries.

npm install jsonwebtoken
npm install jwks-rsa
Enter fullscreen mode Exit fullscreen mode

Let's head into cluster/child.js now and declare those as constants


const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
Enter fullscreen mode Exit fullscreen mode

After this we need to create the jwks client, this is generated based on the .env variables above

const jwksUri = `${process.env.AUTH_URL}/realms/${process.env.AUTH_REALM}/protocol/openid-connect/certs`;

const jwks = jwksClient({
    strictSsl: true, // Default value
    jwksUri: jwksUri
});

const kid = 'OHNidHJInGLWbWHanztSf8A8GDfnJVATENxKjchqvc0';
Enter fullscreen mode Exit fullscreen mode

The last variable kid is the client id in Keycloak.

Next is actually protecting the socket from unauthorized requests

...
io.on('connection', (socket) => {

    let token = socket.handshake.query.token;

    try {
        jwks.getSigningKey(kid, (err, key) => {
            const signingKey = key.getPublicKey();


            jwt.verify(token, signingKey, {
                azp: 'game',
                issuer: `${process.env.AUTH_URL}/realms/${process.env.AUTH_REALM}`,
                algorithms: ['RS256']
            }, function (err, decoded) {
                if (!err)
                    console.log("User authorized", decoded);
            });
        });
    } catch (e) {
        socket.disconnect()
    }

    ...

}
Enter fullscreen mode Exit fullscreen mode

This little chunk gets the token from the query, we then attempt to get the signing key from Keycloak, once we have that we then verify the token against the azp and issuer. Is anything goes wrong, we kick them out of the socket.

The frontend

The frontend doesn't require much code at all as we pull in two packages, one that handles the logic of tokens such as refreshing them. For this we use vue-keycloak-js and also vue-socket.io which handles the logic of binding events to components. All of this lives inside main.js in the client side.

Vue.use(VueKeyCloak, {
    config: {
        authRealm: 'divinity-world',
        authUrl: 'http://localhost:8080/auth',
        authClientId: 'game',
        redirectUri: "http://localhost:8081/play"
    },
    onReady: (keycloak) => {
        console.log({keycloak});


        Vue.use(new VueSocketIO({
            debug: true,
            connection: 'http://localhost:3994?token=' + keycloak.token,
        }));

        new Vue({
            router,
            render: h => h(App)
        }).$mount('#app')
    }
});
Enter fullscreen mode Exit fullscreen mode

An improvement on this would be to also use environment variables in Vue, you may need to change the config depending on your realms.

By wrapping the app in Vue Keycloak, if the user is not authenticated it will always redirect to the login page so it's quite seamless when it comes to integrating it into other applications.

Top comments (0)