Hyperfocus

Sign players in with Velora from the Hyperfocus launcher

If a player is logged in to Hyperfocus, your game can sign them in with Velora without a login screen: the launcher gives the game its own Velora token in the environment variable HF_VELORA_TOKEN (and the username in HF_VELORA_USER) when it starts the game. This works for official games and for community games. You choose the permissions; the player approves them; your game gets a token for exactly those.

The launcher's own login is never shared.The game gets a separate token made just for it (a child of the launcher login, or one the player approved on Velora for the game). It is limited to the permissions you requested, it is tied to your project name, and the player can revoke it.

How it works

  1. You turn on veloraToken for the game with a project name and a permissions number.
  2. The player presses Play. Community games show a permission request the first time; official games only show one if they want more than the launcher's own login has.
  3. Hyperfocus asks Velora for a token for your project (or opens Velora so the player can approve permissions the launcher does not have) and starts the game with HF_VELORA_TOKEN and HF_VELORA_USER set.
  4. Your game reads the variable, calls Velora to check it, and the player is signed in.

Versions: official games get the environment variables with Hyperfocus 1.3.23 or newer. Community games need Hyperfocus 1.3.33-beta.3 or newer (currently a beta, so players on the release channel do not have it yet and the game simply starts without a token).

1. Turn it on

Community games

In the community editor, open your game's details tab and find Velora login for your game. Tick the checkbox, type a project name and tick the permissions your game needs (one checkbox per permission, with what it allows). Sign-in itself (See your profile) is always required. Save.

Official games (the game list)

Official games are listed in apps.json on the Hyperfocus server and managed by the owner, who adds the same field to the game's entry:

{
  "id": "mygame",
  "name": "My Game",
  "veloraToken": { "project": "My Game", "scopes": 17 }
}

scopes is the sum of the permission numbers in the table below. 17 is 1 (profile) plus 16 (read saved data). Leave it out and no token is made.

2. Choose your permissions

Ask for the least you need: players see every line before they press Allow. You can work out the number with the permissions calculator or just add the values below.

NumberPermissionWhat the player is toldIn the launcher's login?
1PROFILE_READSee your profile: username, picture and public profileYes (always required)
2PROFILE_SENSITIVE *See private profile details, like your email addressNo
4PROJECT_AUTHSign you in to other appsNo
8ACCOUNT_WRITE *Change your account settingsNo
16STORAGE_READRead its saved data (what it stored in your Velora storage)Yes
32STORAGE_WRITE *Save data to your account (uses your Velora storage)Yes
64VMAIL_READRead your messagesYes
128VMAIL_SENDSend messages as youYes
256FRIENDS_READSee your friendsYes
512FRIENDS_MANAGE *Add and remove friendsYes
1024CURRENCY_PAY *Spend your Velora credits (payments still ask you first)No
2048CHATChat as youYes
4096MEDIA_UPLOADUpload pictures and mediaYes
8192CURRENCY_READSee your Velora credits (your balance)No
16384VOICEJoin voice chats and call peopleYes
32768SIGN_MESSAGES *Signing Ember chat messages with a device key (the launcher's prompt has no line for it; it is not useful for games)No

* Marked as dangerous on Velora: unchecked by default on Velora's own consent screen. The numbers come from the Velora server; the "In the launcher's login?" column is what the launcher currently asks for when the player signs in to Hyperfocus (the sum is 23537). Permissions that column says "No" for can only reach your game after the player approves them on Velora's page.

Keep the project name and permission number stable. When the number changes, community games ask the player again.

3. The permission prompt

Community games: the first launch always asks

The first time a player launches a community game that uses Velora login, Hyperfocus shows a permission request window: "<game> wants to sign in with your Velora account", the game's icon and author, and every permission the game asked for. The player picks:

The answer is remembered for this game, project name, permission number and Velora account. If you change the permissions, or the player logs in as someone else, it asks again. Players can change their mind under the game's Game settings in Hyperfocus (Revoke or Ask again).

Official games: only for what the launcher login lacks

Official games start without a prompt as long as the launcher login already has every permission they ask for. If the game wants more, the player sees the same window (with the new permissions tagged) and chooses Allow, Play without them (the game gets a token with only the permissions the launcher has) or Cancel. An approved game login is saved encrypted on the player's PC and reused while it is still valid and belongs to the same account.

4. What your game receives

VariableValue
HF_VELORA_TOKENA Velora token for your project. Treat it as a password.
HF_VELORA_USERThe player's Velora username. Convenience only.

Both are missing when:

Always keep a way to sign in that does not need the variable. It is also a good idea to read both variables once at startup and remove them from the environment so child processes do not inherit the token.

5. Validate the token

The token is a normal Velora bearer token. Check it with Velora before you trust anything about the player:

POST https://velora.ikelene.net/auth/validate
Authorization: Bearer <HF_VELORA_TOKEN>
Content-Type: application/json

{}

A good token answers 200 with { "valid": true, "user": { ... } }; a bad or expired one answers 401 with { "valid": false }. From user use:

Read only id, username, rank, pfp_url, color, scopes and project_name. A token without the PROFILE_SENSITIVE permission only gets the public profile: no email address, no Discord ID, no credits, no login statistics and no suspension information. To receive the email you must ask for PROFILE_SENSITIVE (number 2), which always asks the player on Velora's own page.

If you have a game server, validate there.The client can send any username it likes. Have the game send the token to your server over HTTPS and let your server call /auth/validate. Never send the token anywhere except your own server and velora.ikelene.net.

6. Use the token

Send it as Authorization: Bearer <token> on Velora API calls, within the permissions it has: for example storage with the storage permissions, or friends with FRIENDS_READ. See the developer docs and the Velora API guide for the routes.

7. Falling back to your own sign-in

When there is no token, sign the player in the normal Velora way: ask Velora for a code, open the sign-in page in the browser, and poll until the player approves. This is the same flow Hyperfocus itself uses.

// 1. POST /auth/ext-code { permissions_integer, project_name }  -> { code }
// 2. open  https://velora.ikelene.net/login?code=...&project=...&permissions=...
// 3. poll  GET /auth/ext-code/status?code=...  until { ready: true, token }   (the code lives about 5 minutes)

const API = 'https://velora.ikelene.net';

async function signInYourself(project, scopes, openInBrowser) {
  const res = await fetch(`${API}/auth/ext-code`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ permissions_integer: scopes, project_name: project }),
  });
  const { code } = await res.json();
  openInBrowser(`${API}/login?code=${encodeURIComponent(code)}&project=${encodeURIComponent(project)}&permissions=${scopes}`);
  for (let i = 0; i < 200; i++) {                    // about 5 minutes
    await new Promise(r => setTimeout(r, 1500));
    const s = await fetch(`${API}/auth/ext-code/status?code=${encodeURIComponent(code)}`);
    if (s.status === 404) return null;               // the code expired
    const j = await s.json();
    if (j.ready && j.token) return j.token;
  }
  return null;
}

Save the token you get this way somewhere private (Electron's safeStorage, the Windows credential store, an encrypted file) and try it first on the next launch, after the launcher's token.

Examples

All examples do the same: read the variable, ask Velora who it belongs to, and fall back when anything is missing.

Node.js / Electron (main process)

const API = 'https://velora.ikelene.net';
const PROJECT = 'My Game';
const NEED = 1;                                   // PROFILE_READ

// read once, then take it out of the environment so child processes do not inherit it
const launcherToken = process.env.HF_VELORA_TOKEN || null;
delete process.env.HF_VELORA_TOKEN;

async function validate(token) {
  try {
    const res = await fetch(`${API}/auth/validate`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
      body: '{}',
    });
    if (!res.ok) return null;
    const data = await res.json();
    const u = data.valid ? data.user : null;
    if (!u || u.project_name !== PROJECT || (u.scopes & NEED) !== NEED) return null;
    return u;
  } catch {
    return null;
  }
}

async function getLogin() {
  if (launcherToken) {
    const user = await validate(launcherToken);
    if (user) return { token: launcherToken, user };
  }
  const token = await signInYourself(PROJECT, NEED, url => require('electron').shell.openExternal(url));
  const user = token && await validate(token);
  return user ? { token, user } : null;
}

Python

import json
import os
import urllib.error
import urllib.request

API = "https://velora.ikelene.net"
PROJECT = "My Game"
NEED = 1  # PROFILE_READ

token = os.environ.get("HF_VELORA_TOKEN")
os.environ.pop("HF_VELORA_TOKEN", None)  # do not pass it on to child processes


def validate(token):
    req = urllib.request.Request(
        API + "/auth/validate",
        data=b"{}",
        method="POST",
        headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as res:
            data = json.load(res)
    except (urllib.error.URLError, ValueError):
        return None
    user = data.get("user") if data.get("valid") else None
    if not user or user.get("project_name") != PROJECT or (user.get("scopes", 0) & NEED) != NEED:
        return None
    return user


user = validate(token) if token else None
if user:
    print("Signed in as", user["username"])
else:
    print("No launcher login - use your own sign-in")

Godot 4 (GDScript)

extends Node

const API := "https://velora.ikelene.net"
const PROJECT := "My Game"
const NEED := 1  # PROFILE_READ

var username := ""
var token := ""

func _ready() -> void:
	token = OS.get_environment("HF_VELORA_TOKEN")
	if token == "":
		_use_own_sign_in()
		return
	var http := HTTPRequest.new()
	add_child(http)
	http.request_completed.connect(_on_validated)
	var headers := PackedStringArray(["Authorization: Bearer " + token, "Content-Type: application/json"])
	if http.request(API + "/auth/validate", headers, HTTPClient.METHOD_POST, "{}") != OK:
		_use_own_sign_in()

func _on_validated(result: int, code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
	if result != HTTPRequest.RESULT_SUCCESS or code != 200:
		_use_own_sign_in()
		return
	var data = JSON.parse_string(body.get_string_from_utf8())
	var user = data.get("user") if typeof(data) == TYPE_DICTIONARY and data.get("valid", false) else null
	if user == null or user.get("project_name", "") != PROJECT or (int(user.get("scopes", 0)) & NEED) != NEED:
		_use_own_sign_in()
		return
	username = str(user["username"])
	print("Signed in as ", username)

func _use_own_sign_in() -> void:
	token = ""
	# show your own Velora sign-in here (see "Falling back to your own sign-in")
	pass

This runs in a Godot export launched by Hyperfocus. In the editor OS.get_environment returns an empty string, which takes the fallback path.

Unity (C#)

using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class VeloraLogin : MonoBehaviour
{
    const string Api = "https://velora.ikelene.net";
    const string Project = "My Game";
    const int Need = 1; // PROFILE_READ

    [Serializable] class User { public string username; public int scopes; public string project_name; }
    [Serializable] class Reply { public bool valid; public User user; }

    public string Username { get; private set; }

    IEnumerator Start()
    {
        string token = Environment.GetEnvironmentVariable("HF_VELORA_TOKEN");
        if (string.IsNullOrEmpty(token)) { UseOwnSignIn(); yield break; }

        using (var req = new UnityWebRequest(Api + "/auth/validate", "POST"))
        {
            req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes("{}"));
            req.downloadHandler = new DownloadHandlerBuffer();
            req.SetRequestHeader("Content-Type", "application/json");
            req.SetRequestHeader("Authorization", "Bearer " + token);
            yield return req.SendWebRequest();

            if (req.result != UnityWebRequest.Result.Success) { UseOwnSignIn(); yield break; }
            var reply = JsonUtility.FromJson<Reply>(req.downloadHandler.text);
            if (reply == null || !reply.valid || reply.user == null
                || reply.user.project_name != Project || (reply.user.scopes & Need) != Need)
            { UseOwnSignIn(); yield break; }

            Username = reply.user.username;
            Debug.Log("Signed in as " + Username);
        }
    }

    void UseOwnSignIn()
    {
        // show your own Velora sign-in here (see "Falling back to your own sign-in")
    }
}

Environment variables are not available in the Unity editor unless you set them there, and a WebGL build cannot read them at all.

TurboWarp and PenguinMod games

A project packaged with the TurboWarp or PenguinMod packager runs inside a web page, which cannot read the launcher's environment variables. The Hyperfocus Scratch extension does not hand out a token either: its player blocks (logged in to Velora?, Velora username, picture, colour, rank) are for display and are not proof of who the player is.

For a Scratch project that needs a real login, use the Velora extension inside the project itself: it opens Velora's sign-in window and gives the project a token with the permissions you choose. The steps are in How to Add Velora Login to Your PenguinMod Project and the TurboWarp login extension guide. In a packaged app the Velora extension needs an internet connection like any other Velora call.

Troubleshooting

ProblemLikely cause
The variable is emptyThe game was not started by Hyperfocus, the player is logged out or offline, the player chose Play without it, or the game was opened with something other than an .exe, .cmd or .bat file.
No permission prompt appearsCommunity games always ask on the first launch; if it never shows, check that veloraToken is saved on the details tab and the game was reloaded in Hyperfocus (F5 or refocus the window). Official games only ask for permissions beyond the launcher's login.
/auth/validate returns 401The token was revoked, expired after 7 days without use, or is not a Velora token.
The scopes are fewer than you asked forThe player chose Play without them (official games), or an administrator switched a service off for the account. Check scopes and degrade gracefully.
It asks the player every launchThe project name or permission number changes between launches, or the player keeps using different Velora accounts.

Questions

Is the token the launcher's own login?

No. The game gets its own token, limited to the permissions you asked for and to what the player allowed. Hyperfocus never hands a game its own token or the player's password.

What if the player says no?

For community games the player can choose Play without it, and the game starts with no token. Your game must then offer its own sign-in. The same applies when the player is not logged in to Hyperfocus, is offline, or Velora is unreachable.

Can I trust HF_VELORA_USER?

Only as a display name. If anything depends on who the player is, validate HF_VELORA_TOKEN with POST /auth/validate, and do it on your own server if you have one.

Does this work for TurboWarp and PenguinMod games?

Not through the environment variable: Scratch projects cannot read it, and the Hyperfocus extension does not return a token. Use the Velora extension's own sign-in blocks inside the project.

How long does the token last?

A Velora token stops working after 7 days without being used, and the player can revoke it. Hyperfocus makes a fresh one each time the game starts, so read the variable on every launch.

More Hyperfocus guides