Auth Module (@colyseus/auth
)¶
The @colyseus/auth
module is highly configurable and allows you to implement your own authentication backend.
This module is currently in beta
Feedback is welcome on colyseus/colyseus#660.
Features¶
- Client-side APIs (via
client.auth
) - Email/Password Authentication
- Anonymous Authentication
- Forgot Password + Password Reset
- OAuth 2.0 providers (200+ supported providers, including Discord, Google, Twitter, etc.)
Backend configuration you need to provide¶
- Environment secrets
- Storing and querying users from/into your database
- Sending emails (for "Email Verification" and "Password Reset")
- OAuth 2.0 providers (for OAuth authentication)
Example Project using @colyseus/auth
The Webgame Template repository contains a complete usage example for both server-side and client-side.
Installation¶
Install the @colyseus/auth
module:
Usage¶
It is required to bind the authentication routes to Express.
Client-side API (client.auth
)¶
Backend configuration required
None of the client-side APIs below will work unless you configure your backend. See example configuration from the Webgame Template project.
→ client.auth.registerWithEmailAndPassword(email, password, options?)
¶
Register a new user with email/password and return userdata. The user will be automatically logged in after registration. This method modifies the client.auth.token
property.
The options
argument is optional and may contain data you can use when creating the user's account.
→ client.auth.signInWithEmailAndPassword(email, password)
¶
Sign in with email/password and return userdata. This method modifies the client.auth.token
property.
→ client.auth.signInAnonymously(options?)
¶
Sign in anonymously and return anonymous userdata. This method modifies the client.auth.token
property.
→ client.auth.signInWithProvider(provider)
¶
Sign in with OAuth provider and return userdata. This method modifies the client.auth.token
property.
The OAuth authentication flow
- A popup window to is opened
/auth/provider/[PROVIDER-ID]
- The user is redirected to the OAuth provider's website
- The user authenticates with the OAuth provider
- The user is redirected back to
/auth/provider/[PROVIDER-ID]/callback
(see OAuth Provider Callback) - The popup window is closed and userdata is returned
→ client.auth.sendPasswordResetEmail()
¶
→ client.auth.getUserData()
¶
→ client.auth.onChange()
¶
Define a callback that is triggered when internal auth state changes. It only triggers as a response from client.auth
method calls - this is not a realtime subscription.
→ client.auth.signOut()
¶
Clear the authentication token from the client-side.
→ client.auth.token
¶
The authentication token is automatically sent to the server on every request. Operations that result in a user being logged in will set the client.auth.token
property, which is a JWT token containing the user's data. The contents of this token are
The JWT token is cached and reloaded on page refresh.
The JWT token is stored in the localStorage
of the browser.
Backend configuration¶
Environment Secrets and Security Concerns¶
It is required to provide the following environment secrets:
AUTH_SALT
- Used to hash the user's password. (scrypt
algorithm by default)JWT_SECRET
- Used to sign the JWT token.SESSION_SECRET
- Used to sign the session cookie. (only used during OAuth flow)
How to generate a random string
You may use the following command to generate a random string openssl rand -base64 32
. Alternatively, you can use an online strong password generator.
Keep your secrets safe¶
The exposure of these secrets may lead to security breaches on your application. Make sure to never expose them publicly, and limit the number of people in your team who have access to them.
If any of these secrets are compromised, you must rotate them immediately. The implications of rotating them are:
- Rotating
AUTH_SALT
will invalidate all user's passwords. Users will need to reset their password. - Rotating
JWT_SECRET
will invalidate all JWT tokens. Users will need to login again. - Rotating
SESSION_SECRET
will invalidate all session cookies. (only used during OAuth flow)
Email/Password Authentication¶
In order to allow email/password authentication, you must implement the following callbacks:
auth.settings.onFindUserByEmail
: to query your database for the user's by its email addressauth.settings.onRegisterWithEmailAndPassword
: to insert a new user into your database
→ onFindUserByEmail
setting¶
Use this callback to query your database for the user's by its email address. (The database module is not provided by this module, you must provide your own.)
Return value: It must return the user entry from the database, with password
field included. All values, except from the password
will be encoded in the JWT token. It is recommended to return at least the user's id
+ password
from your database, although you can store more fields for convenience sake.
Error: If null
or undefined
is returned, user will receive invalid_credentials
error message. You may throw yourself a different error by using throw new Error("your_error_message")
.
import { auth } from "@colyseus/auth";
auth.settings.onFindUserByEmail = async function (email) {
return await User.query().selectAll().where("email", "=", email).executeTakeFirst();
}
→ onRegisterWithEmailAndPassword
setting¶
Use this callback to insert a new user into your database. The password is already hashed.
If throwing an error, the error message will be sent to the client.
import { auth } from "@colyseus/auth";
auth.settings.onRegisterWithEmailAndPassword = async function (email, password, options) {
return await User.insert({ name, email, password, });
}
Anonymous Authentication¶
Anonymous authentication is enabled by default. You may customize how the anonymous user is created by providing the onRegisterAnonymously
callback.
By default, the anonymous user will have the following fields on its JWT token payload:
→ onRegisterAnonymously
setting¶
You may use this callback to customize the JWT token payload for anonymous users. The fields returned by this callback will be available in the JWT token as payload.
On the example below the anonymous user is being inserted into the database, and its userId
is being returned as payload.
import { generateId } from "colyseus";
import { auth } from "@colyseus/auth";
auth.settings.onRegisterAnonymously = async function (options) {
const userId = await User.insert({ anonymous: true });
return { userId };
}
Email Verification¶
You may enable email verification by providing both onSendEmailConfirmation
and onEmailConfirmed
callbacks.
It is your responsibility to limit the user access to your application until their email is verified.
Email verification is not mandatory
Users are allowed to login without verifying their email address. If you require email verification, you must validate if the user's email is verified on your application.
→ onSendEmailConfirmation
setting¶
Use this callback to send the email verification to the user.
Argument | Description |
---|---|
email |
the email address of the user |
html |
the HTML contents of the email (uses the address-confirmation-email.html template) |
link |
the URL to confirm the email address (optional, the template already includes this URL) |
import { auth } from "@colyseus/auth";
auth.settings.onSendEmailConfirmation = async function(email, html, link) {
// send email to the user (example using resend.com)
await resend.emails.send({
to: email,
subject: '[Your project]: Confirm your email address',
from: 'no-reply@your-domain.io',
html: htmlContents,
});
}
→ onEmailConfirmed
setting¶
This this callback to update the user's database record as verified.
Argument | Description |
---|---|
email |
the email address of the user |
import { auth } from "@colyseus/auth";
auth.settings.onEmailConfirmed = async function(email) {
// update user database record as verified
await User.update({ verified: true }).where("email", "=", email).execute();
}
Forgot Password¶
To enable "Forgot Password" feature, you must provide the following callbacks:
auth.settings.onForgotPassword
: to send the email to the userauth.settings.onResetPassword
: to update the user's password
How it works
The link to reset the password is sent to the user's email address. The link contains a JWT token with the user's email address as payload. The user is then redirected to a page where they can enter a new password. The token expires in 30 minutes and can't be re-used.
→ onForgotPassword
setting¶
Use this callback to send the "forgot password" email to the user. The email template used is reset-password-email.html
.
import { auth } from "@colyseus/auth";
auth.settings.onForgotPassword = async function (email: string, html: string/* , resetLink: string */) {
await resend.emails.send({
to: email,
subject: '[Your project]: Reset password',
from: 'no-reply@your-domain.io',
html: html
});
}
→ onResetPassword
setting¶
Use this callback to update the user's password. The password is already hashed.
import { auth } from "@colyseus/auth";
auth.settings.onResetPassword = async function (email: string, password: string) {
await User.update({ password }).where("email", "=", email).execute();
}
OAuth providers (Discord, Google, X/Twitter, etc)¶
In order to enable OAuth authentication, you must add at least one OAuth provider, and implement the auth.oauth.onCallback
callback.
This module supports 200+ OAuth 2.0 providers
This module leverages the hard work of simov on his grant open-source module, which supports 200+ OAuth 2.0 providers.
You may check the original Grant Playground to experiment with scopes and OAuth configuration.
Add OAuth Provider (via auth.oauth.addProvider()
)¶
Argument | Description |
---|---|
providerId |
the provider ID (e.g. "discord", "google", "twitter", etc) |
options |
the provider options, may vary depending on the provider (see below) |
import { auth } from "@colyseus/auth";
auth.oauth.addProvider('[PROVIDER-ID]', {
key: "XXXXXXXXXXXXXXXXXX", // Client ID
secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Client Secret
scope: ['identify', 'email'],
});
OAuth Provider Callback (auth.oauth.onCallback
)¶
Use this callback to create the user's account after the OAuth provider redirects the user back to your application.
You must configure the "Redirect URL" on the OAuth provider's dashboard to point to the following URL:
Redirect URL on different environments
It is recommended that you create a different OAuth application for development and production environments. This way you can configure the "Redirect URL" to point to http://localhost:2567/auth/provider/[PROVIDER-ID]/callback
during development, and https://[YOUR-DOMAIN]/auth/provider/[PROVIDER-ID]/callback
on production.
Argument | Description |
---|---|
data |
the OAuth data (e.g. profile , access_token , etc) |
providerId |
the provider ID (e.g. "discord" , "google" , "twitter" , etc) |
import { auth } from "@colyseus/auth";
auth.oauth.onCallback(async (data, provider) => {
const profile = data.profile;
return await User.upsert({
discord_id: profile.id,
name: profile.global_name || profile.username,
locale: profile.locale,
email: profile.email,
});
});
Example: Discord¶
To enable Discord authentication, you must create a new application at Discord Developer Portal.
Under the "Settings -> OAuth2" you will find the Client ID (key
) and Client Secret (secret
), that must be used to configure the provider:
auth.oauth.addProvider('discord', {
key: "XXXXXXXXXXXXXXXXXX", // Client ID
secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", // Client Secret
scope: ['identify', 'email'],
});
You will also need to configure the "Redirect URL" so Discord can redirect the user back to your application after authentication. The URL must be in the following format:
Advanced Settings¶
You may customize the following settings:
auth.settings.onParseToken
: to parse JWT token provided by the client-sideauth.settings.onGenerateToken
: to generate the auth tokenauth.settings.onHashPassword
: to hash the user's password
→ onParseToken
setting¶
Use this callback to parse the token provided by the client-side. The token is already verified and decoded.
import { auth } from "@colyseus/auth";
auth.settings.onParseToken = async function (jwt) {
return jwt;
}
→ onGenerateToken
setting¶
Use this callback to generate the token from the user's data. The token is already verified and decoded.
import { auth } from "@colyseus/auth";
auth.settings.onGenerateToken = async function (userdata) {
return JWT.sign(userdata);
}
→ onHashPassword
setting¶
Use this callback to hash the user's password.
import { auth } from "@colyseus/auth";
auth.settings.onHashPassword = async function (password: string) {
return Hash.make(password);
};
Protecting an HTTP route (via auth.middleware()
)¶
You may protect an HTTP route by using the auth.middleware()
middleware. Only authenticated users will be able to access the route.
Customize Email Templates¶
You can customize the email templates by providing your own templates under the html
directory.
my-colyseus-app/
├─ html/
│ ├─ address-confirmation-email.html
│ ├─ address-confirmation.html
│ ├─ reset-password-email.html
│ ├─ reset-password-form.html
├─ package.json
It is recommended to copy the default templates from the @colyseus/auth
package, and customize them to your needs:
- html/address-confirmation-email.html → The email sent to the user to confirm their email address
- html/address-confirmation.html → The page where the user is redirected to confirm their email address
- html/reset-password-email.html → The email sent to the user to reset their password
- html/reset-password-form.html → The page where the user is redirected to reset their password
Upgrading and linking user accounts¶
You may use the contents of the previous active token (upgradingToken
) when registering an user via email/password or OAuth.
- Upgrade an anonymous user to an email/password or OAuth account
- Link multiple OAuth providers to the same account
Example