01Home.md

Introduction

The goals of this project:

  • Provide tools for modifying Minecraft to use a custom Yggdrasil service.
  • Provide technical specifications for custom Yggdrasil server implementations and launchers using custom Yggdrasil services.
  • Provide a unified login experience outside of Mojang's official system for players.

    • Players can use any launcher that implements this specification to log in to any Yggdrasil service that implements the specification.

This project will provide detailed documentation on all APIs and also define some APIs that do not belong to Yggdrasil. This is done to simplify the process of specifying a Yggdrasil service: you only need to provide the URL of the corresponding Yggdrasil service to use it.

If you are a Yggdrasil server developer, launcher developer, or interested in this project, please watch this project to stay updated on the latest developments of the specifications.

Developer Communication QQ Group: 926979364, Telegram Group: @authlib_injector. Launcher or skin site developers are welcome to join. Ordinary users should not join, as it may be difficult to understand.

  • yggdrasil-mock

    • A reference implementation of the Yggdrasil server specification and test cases for Yggdrasil APIs.
    • Demo site for Yggdrasil server based on this project: auth-demo.yushi.moe
  • BMCLAPI

    • BMCLAPI provides a mirror for downloading authlib-injector.
  • Yggdrasil API for Blessing Skin

    • Yggdrasil plugin for the Blessing Skin site.
  • HMCL

    • HMCL v3.x supports authlib-injector.
  • BakaXL

    • BakaXL 3.0 supports authlib-injector.
  • LaunchHelper

    • If you want to use authlib-injector on a Multicraft panel server, you can try this project.

Donations

BMCLAPI provides a mirror site for downloading authlib-injector. If you would like to support the development of authlib-injector, you can donate to BMCLAPI.

Getting-authlib-injector.md

Manual Download

The latest version of authlib-injector can be downloaded directly from authlib-injector.yushi.moe.

Download API

The authlib-injector project provides a set of APIs for downloading authlib-injector builds. The API endpoint is https://authlib-injector.yushi.moe/.

Get Version List

GET /artifacts.json

Response format:

{
    "latest_build_number": latest build number,
    "artifacts": [ // information about each version
        {
            "build_number": build number of this version,
            "version": "version number of this version"
        }
        // ,... (more versions can be present)
    ]
}

Get Specific Version

GET /artifact/{build_number}.json

The {build_number} parameter in the URL represents the build number of the version.

Response format:

{
    "build_number": build number of this version,
    "version": "version number of this version",
    "download_url": "download URL for this version of authlib-injector",
    "checksums": { // checksums
        "sha256": "SHA-256 checksum"
    }
}

Get Latest Version

GET /artifact/latest.json

Response format is the same as above. Use this API if you only need to get the latest version.

BMCLAPI Mirror

When using BMCLAPI, please comply with the BMCLAPI terms.

BMCLAPI provides a mirror for this download API, with the endpoint at https://bmclapi2.bangbang93.com/mirrors/authlib-injector/.

Launcher-Technical-Specification.md

Overview

This document aims to provide technical guidance for implementing the authlib-injector specification in launchers. Since this feature requires calling the Yggdrasil API, it is recommended that you first read the Yggdrasil Server Technical Specification before reading this document.

In the launcher, this login method can be referred to as External Login (authlib-injector) or authlib-injector Login. We recommend using the clearer term, the former.

Authentication Server

The authentication server (i.e., the Yggdrasil server) is the core of the entire authentication system, and all authentication-related requests will be sent to it.

To specify an authentication server, the launcher should store the server's API address (i.e., API Root, such as https://example.com/api/yggdrasil/).

The launcher may support only one authentication server or multiple servers. Supporting multiple authentication servers means the launcher can hold multiple accounts simultaneously, and these accounts can belong to different authentication servers.

Authentication Server Setup

Setting up the authentication server is generally done by the player, but there are cases where the server owner sets it up or the configuration file is distributed along with the launcher and game. Below are several ways to set the authentication server:

Specified in Configuration File

The launcher can store the API address directly in the configuration file, allowing users to set the authentication server by editing this file. This method is simple to implement and suitable if your launcher is used only as a dedicated server launcher.

Input Address in the Launcher

In this method, users enter the URL in the launcher to set the authentication server. The URL can be a full API address (e.g., https://example.com/api/yggdrasil/) or a shortened address (e.g., example.com).

If the URL does not specify a protocol (HTTPS or HTTP), it is conventionally auto-completed as HTTPS. That is, example.com/api/yggdrasil/ should be understood as https://example.com/api/yggdrasil/.

For security reasons, the launcher must not downgrade to plain HTTP if it cannot connect via HTTPS.

Also, authlib-injector defines a service discovery mechanism called API Location Indication (ALI). It converts the user-inputted abbreviated or incomplete address into a complete API address.

Handling API Location Indication (ALI)

To resolve the user-inputted address into the actual API address, the launcher must:

  1. Add HTTPS protocol if missing.
  2. Send a GET request to the URL (following HTTP redirects).
  3. If the response contains the ALI header (X-Authlib-Injector-API-Location), then the URL indicated by ALI is the API address.

    • X-Authlib-Injector-API-Location can be an absolute or relative URL.
    • If ALI points to itself, it means the current URL is the API address.
  4. If the response lacks the ALI header, assume the current URL is the API address.

Pseudocode:

function resolve_api_url(url)
    response = http_get(url) // follow redirects

    if response.headers["x-authlib-injector-api-location"] exists
        new_url = to_absolute_url(response.headers["x-authlib-injector-api-location"])
        if new_url != url
            return new_url

    return url

Setting via Drag-and-Drop

This method allows users to set the authentication server by dragging and dropping (DnD) with a mouse.

The DnD source can be a browser or another application, while the DnD target is the launcher. The source should present some text, image, or other content that indicates to the user to drag it into the launcher to add an authentication server. During this, authentication server info is transferred from the source to the launcher. After the drag-and-drop, the launcher asks the user to confirm adding this authentication server.

Drag Data

The MIME type for the dragged data is text/plain, containing a URI formatted as:

authlib-injector:yggdrasil-server:{API address of the authentication server}

The API address must be encoded.

The drag effect should be copy.

HTML Example

Demo page

Add draggable="true" to the DOM element and handle the dragstart event:

<span id="dndLabel" draggable="true" ondragstart="dndLabel_dragstart(event);">example.yggdrasil.yushi.moe</span>
function dndLabel_dragstart(event) {
    let yggdrasilApiRoot = "https://example.yggdrasil.yushi.moe/";
    let uri = "authlib-injector:yggdrasil-server:" + encodeURIComponent(yggdrasilApiRoot);
    event.dataTransfer.setData("text/plain", uri);
    event.dataTransfer.dropEffect = "copy";
}

Displaying Authentication Server Information

By sending a GET request to the API address, the launcher can obtain metadata from the authentication server (see response format), such as the server name. The launcher can use this metadata to improve user experience.

Displaying Server Name

The authentication server specifies its name in the serverName field inside meta. When the launcher needs to show an authentication server to the user, it can display this name.

Note that server names might conflict, so the launcher should provide a way to view the API address of the authentication server, e.g., showing the API address in a tooltip when hovering over the server name.

Warning for Non-HTTPS Authentication Servers

When a user attempts to set an authentication server using plain HTTP, the launcher should prominently warn the user that this may compromise their security, as passwords will be transmitted in plaintext.

Accounts

An account corresponds to a player in the game, and users can select an account at launch to play.

Relationship between accounts, users, and profiles: The concept of an account in the launcher is not the same as the user concept on the authentication server. The launcher’s account corresponds to a profile (role) on the authentication server. A user on the server owns one or more profiles and does not have a corresponding entity in the launcher.

Account Information Storage

The launcher identifies an account by the following three immutable attributes:

  • Authentication server to which the account belongs
  • Account identifier (e.g., email)

    • Usually, the account identifier is the email. However, if the feature.non_email_login field in the server metadata is true, it means the server supports credentials other than email for login, so the account identifier may not be an email. In that case, the launcher should not assume the user input is an email and should carefully choose terminology (e.g., use "account" instead of "email") to avoid confusion. (See Yggdrasil Server Specification § Login Using Profile Name)
  • UUID of the profile corresponding to the account

Two accounts are considered identical only if all three attributes match. One matching attribute alone does not mean two accounts are the same. Multiple profiles can exist on the same authentication server; multiple profiles can belong to one user; different servers may have profiles with the same UUID. Therefore, the launcher must use all three attributes together to identify an account.

In addition to these three, accounts have the following attributes:

  • Tokens (accessToken and clientToken)
  • Name of the corresponding profile
  • User ID
  • User properties

Security warning:

  • The stored login state records tokens, not the user password. Passwords should never be stored in plaintext at any time.

These attributes are mutable. After each login or refresh operation, the launcher must update the stored account attributes.

In all login and refresh operations below, the requestUser parameter in the request should be true so the launcher can immediately update user ID and user properties.

Adding Accounts

If a user wants to add an account, the launcher needs to ask for the authentication server, the user’s account, and password. The authentication server can be pre-configured, chosen from a server list, or set by the user on the spot (see above).

Then the launcher proceeds as follows:

  1. Call the authentication server’s login API with the user’s account and password.
  2. If the response’s selectedProfile is not null, login is successful; update account attributes from the response and finish.
  3. If availableProfiles is empty, the user has no profiles; throw an exception.
  4. Prompt the user to select a profile from availableProfiles.
  5. Call the refresh API with the token from the login and the selected profile.
  6. Login succeeds; update account attributes from the refresh response.

Validating Credentials

Before using credentials (e.g., before launching the game), the launcher must verify their validity. If invalid, the user must log in again. The validation steps:

  1. Call the validate token API with the account’s accessToken and clientToken.
  2. If successful, credentials are valid; finish. Otherwise, continue.
  3. Call the refresh API with accessToken and clientToken.
  4. If successful, update account attributes from the refresh response; finish. Otherwise, continue.
  5. Ask the user to re-enter the password.
  6. Call the login API with the account and password.
  7. If selectedProfile is not null:

    1. If the UUID in selectedProfile matches the account’s profile UUID, update user attributes from the login response; finish.
    2. Otherwise, throw an exception (the original account profile is unavailable).
  8. Find the profile with the same UUID in availableProfiles. If none, throw an exception (original profile unavailable).
  9. Call the refresh API with the login token and selected profile from the previous step.
  10. Login successful; update account attributes.

Displaying Account Information

When showing accounts, the launcher should display not only the profile name but also the authentication server it belongs to (see above for displaying server names) to prevent confusion between identically named profiles on different servers.

If the launcher displays player skins, it can call the query profile properties API to get profile properties, which include skin information.

Launching the Game

Before launching the game, the launcher needs to do the following:

  1. (If needed) Download authlib-injector
  2. Verify credential validity
  3. Prefetch configuration
  4. Add launch parameters

Steps 1, 2, and 3 can be done in parallel to speed up launching.

Download authlib-injector

The launcher may include authlib-injector.jar or download it before launching the game (and cache it). This project provides an API to download authlib-injector.

If your users are mainly in mainland China, we recommend downloading from the BMCLAPI mirror.

Prefetch Configuration

Before launching, the launcher sends a GET request to the API address to get API metadata. This metadata is passed into the game at launch so that authlib-injector does not need to request the authentication server directly, speeding up startup and preventing crashes caused by network failures.

Adding Launch Parameters

Configuring authlib-injector

The launcher must add the following JVM parameters (before the main class arguments):

  1. javaagent parameter:

    -javaagent:{path_to_authlib-injector.jar}={authentication_server_API_address}
  2. Prefetch configuration:

    -Dauthlibinjector.yggdrasil.prefetched={Base64_encoded_API_metadata}

Example using example.yggdrasil.yushi.moe:

  • authlib-injector.jar located at /home/user/.launcher/authlib-injector.jar.
  • Authentication server API address: https://example.yggdrasil.yushi.moe/.
  • Send GET request to https://example.yggdrasil.yushi.moe/ and receive API metadata:

    {"skinDomains":["yushi.moe"],"signaturePublickey":... (omitted)}
  • Base64 encode the above response to get:

    eyJza2luRG9tYWluc... (omitted)
  • JVM parameters to add:

    -javaagent:/home/user/.launcher/authlib-injector.jar=https://example.yggdrasil.yushi.moe/
    -Dauthlibinjector.yggdrasil.prefetched=eyJza2luRG9tYWluc... (omitted)

Replacing Parameter Templates

The game version JSON file (versions/<version>/<version>.json) specifies parameters the launcher uses to start the game. Some authentication-related parameter templates should be replaced as follows:

Parameter Template Replace With
\${auth_access_token} Account’s accessToken
\${auth_session} Account’s accessToken
\${auth_player_name} Profile name
\${auth_uuid} Profile UUID (unsigned)
\${user_type} mojang
\${user_properties} User properties (JSON format) (if unsupported by launcher, replace with {})

Signing-Key-Pairs.md

Overview

This document mainly introduces key pairs used for digital signatures. It uses OpenSSL to operate on the keys.

The authentication server will digitally sign the profile properties in the responses of the following requests:

The authentication server publishes the public key via API metadata so that authlib-injector can obtain it.

Note: The authentication server should avoid changing the keys. If multiple server instances are used for load balancing, they should share the same key.

Generating and Handling Key Pairs

The following OpenSSL commands use standard input and standard output for input and output. If you want to use files, you can use the parameters -in <file> and -out <file>.

Generating the Private Key

The key algorithm is RSA, with a recommended length of 4096 bits.

openssl genrsa 4096

The generated private key will be output to standard output.

Generating the Public Key from the Private Key

openssl rsa -pubout

The private key is read from standard input, and the public key is output to standard output.

Using-authlib-injector-on-Minecraft-Server.md

Getting authlib-injector

First, you need to download the latest version of authlib-injector from here.

Vanilla Server / Spigot / Paper / ...

Since authlib-injector v1.2.0, you need to set enforce-secure-profile to true. This is different from previous versions!

If you encounter chat message signature issues on MC 1.19+, please read: \:point_right: authlib-injector v1.2.0 Upgrade FAQ #174 \:point_left:

Please set online-mode to true in your server’s server.properties. For 1.19+ servers, you also need to set enforce-secure-profile to true.

Then add the following JVM argument to your server startup command (add the argument before -jar):

-javaagent:{path/to/authlib-injector.jar}={https://your-yggdrasil-api-root.com}
  • {path/to/authlib-injector.jar} refers to the location of the JAR file you downloaded in the previous step (can be relative or absolute path).
  • {https://your-yggdrasil-api-root.com} is the URL of your authentication server.

For example, if your original startup command is:

java -jar minecraft_server.1.12.2.jar nogui

Assuming:

  • You downloaded the authlib-injector JAR named authlib-injector.jar.
  • You placed it in the same directory as your server JAR minecraft_server.1.12.2.jar.
  • Your authentication server URL is https://example.yggdrasil.yushi.moe.

Then your new startup command should be:

java -javaagent:authlib-injector.jar=https://example.yggdrasil.yushi.moe -jar minecraft_server.1.12.2.jar nogui

BungeeCord / Velocity

If you are using BungeeCord or Velocity, you need to load authlib-injector on all servers as well as on BungeeCord / Velocity (see above for how), and enable enforce-secure-chat. However, only BungeeCord / Velocity should have online-mode enabled, while the backend MC servers should have online-mode disabled.

Using Mojang Skins

After loading authlib-injector, all skins are by default fetched from the specified authentication server. For example:

  • /give @p minecraft:skull 1 3 {SkullOwner:"notch"}
  • (Citizens2 plugin) /npc skin notch

These commands will get the skin of the character named notch from the custom authentication server.

If you want to use Mojang’s skins, you can append @mojang after the player name, e.g.:

  • /give @p minecraft:skull 1 3 {SkullOwner:"notch@mojang"}
  • /npc skin notch@mojang

For detailed explanation, see the -Dauthlibinjector.mojangNamespace option in README § Parameters.

Accessing Mojang via Proxy

Fetching Mojang skins requires the MC server to be able to access the Mojang API. If your server accesses Mojang through a proxy, you can specify the proxy with the following JVM argument at startup:

-Dauthlibinjector.mojangProxy=socks://<host>:<port>

Note:

  • This proxy is only used when querying Mojang for player info; skin image downloads do not go through the proxy (even if the textures are from Mojang).
  • Currently, only SOCKS5 proxies are supported.

Yggdrasil-Server-Specification.md

Overview

This document aims to provide an unofficial technical specification for implementing a Yggdrasil server.

The server behavior described in this specification may not necessarily match that of Mojang's server. This is objectively because Mojang's server is closed-source, and we can only speculate on its internal logic. Any such speculations may not perfectly align with the actual behavior. However, as long as the client can correctly understand and handle the server's responses, it doesn't matter whether the behavior is identical to Mojang's server.

Basic Agreements

Character Encoding

All characters in this document use UTF-8 encoding.

Request and Response Format

Unless otherwise specified, both requests and responses are in JSON format (if there is a body), and the Content-Type is application/json; charset=utf-8.

All APIs should use the HTTPS protocol.

Error Message Format

{
    "error":"Brief description of the error (machine-readable)",
    "errorMessage":"Detailed description of the error (human-readable)",
    "cause":"The cause of the error (optional)"
}

When encountering an exception already defined in this document, the returned error message should meet the corresponding requirements.

The following table lists error messages for common exception cases. Except for special notes, the cause is generally not included.

Non-standard refers to cases where we cannot trigger the corresponding error in Mojang's Yggdrasil server and can only speculate on the error message in such cases.

Undefined refers to cases where there is no clear requirement for the item.

Exception Case HTTP Status Code Error Error Message
General HTTP error (non-business exceptions, e.g., Not Found, Method Not Allowed) Undefined The HTTP status corresponding Reason Phrase (defined in HTTP/1.1) Undefined
Invalid token 403 ForbiddenOperationException Invalid token.
Incorrect password, or temporary ban after multiple login failures in a short period 403 ForbiddenOperationException Invalid credentials. Invalid username or password.
Trying to assign a role to a token that already has a role assigned 400 IllegalArgumentException Access token already has a profile assigned.
Trying to bind a role that doesn't belong to the corresponding user for a token (Non-standard) 403 ForbiddenOperationException Undefined
Trying to join a server with an invalid role 403 ForbiddenOperationException Invalid token.

Data Format

We define the following data formats:

  • Unsigned UUID: Refers to the UUID string with all - characters removed.

Models

User

A system can have several users, each with the following attributes:

  • ID
  • Email
  • Password

The ID is an unsigned UUID. The email can be changed, but it must be unique.

Serialization of User Information

Serialized user information follows the format below:

{
    "id":"User's ID",
    "properties":[ // User's properties (array, each element is a property)
        { // A property
            "name":"Property name",
            "value":"Property value",
        }
        // ,... (more properties can be added)
    ]
}

The currently known user properties include:

Name Value
preferredLanguage (Optional) The user's preferred language, e.g., en, zh_CN

Role (Profile)

Mojang currently does not support multiple roles and does not guarantee the correctness of any multiple role-related content.

Roles have a many-to-one relationship with accounts. One role corresponds to one player in Minecraft. A role has the following attributes:

  • UUID
  • Name
  • Skin model, optional values: default, slim

    • default: Standard arm width (4px) skin
    • slim: Slim arms (3px) skin
  • Texture

    • A mapping type
    • Key values include: SKIN, CAPE
    • Value is a URL

Both the UUID and name are globally unique, but the name can change. It's recommended to avoid using the name as an identifier.

Role UUID Generation

If compatibility is not a concern, the role's UUID is generally randomly generated (Version 4).

However, Minecraft uses only UUIDs as role identifiers. Roles with different UUIDs are considered different, even if they share the same name. If a Minecraft server migrates from another login system (e.g., official authentication, offline authentication, or others), and the role's UUID changes, the character data will be lost. To prevent this, it is essential to ensure that the UUID generated by this system for a role matches the UUID it had in the previous system.

Compatibility with Offline Authentication

If the Minecraft server originally used offline authentication, the role UUID is a function of the character name. If the Yggdrasil server uses this method to generate role UUIDs, it can achieve bi-directional compatibility between offline authentication systems and this login system, meaning roles can be switched between the offline authentication system and this login system without losing character data.

The code to calculate the role UUID from the character name is as follows (Java):

UUID.nameUUIDFromBytes(("OfflinePlayer:" + characterName).getBytes(StandardCharsets.UTF_8))

Implementation in other languages:

Serialization of Role Information

Serialized role information follows the format below:

{
    "id":"Role UUID (unsigned)",
    "name":"Role name",
    "properties":[ // Role's properties (array, each element is a property) (only needs to be included in specific cases)
        { // A property
            "name":"Property name",
            "value":"Property value",
            "signature":"Digital signature of the property value (only needs to be included in specific cases)"
        }
        // ,... (more can be added)
    ]
}

Role properties (properties) and the digital signature (signature) are not required unless specifically noted.

signature is a digital signature of the property value, encoded in Base64. The signing algorithm is SHA1withRSA, as described in PKCS #1. For more details on the signing key, refer to Signing Key Pair.

Role properties can include the following items:

Name Value
textures (Optional) Base64 encoded JSON string containing the role's texture information, see §textures Texture Information Property.
uploadableTextures (Optional) The types of textures the role can upload, a property defined by authlib-injector, see §uploadableTextures Uploadable Texture Types.

textures Texture Information Property

The following is the format for the texture information. After Base64 encoding this JSON, it becomes the value of the textures role property.

{
    "timestamp": The timestamp when this property was generated (Java timestamp format, i.e., milliseconds since 1970-01-01 00:00:00 UTC),
    "profileId":"Role UUID (unsigned)",
    "profileName":"Role name",
    "textures":{ // Role's textures
        "Texture type (e.g., SKIN)":{
            "url":"URL of the texture",
            "metadata":{ // Metadata of the texture, not needed if absent
                "Name":"Value"
                // ,... (more can be added)
            }
        }
        // ,... (more can be added)
    }
}

Currently known metadata items for textures include model, which indicates the texture model for the role, with values of default or slim.

uploadableTextures Uploadable Texture Types

Note: This role property is defined by the authlib-injector documentation. The role properties returned by Mojang do not include this item. Mojang only allows users to upload skins, not capes.

Since not all authentication servers allow users to upload skins and capes, authlib-injector defines the uploadableTextures role property, which indicates the types of textures the role can upload.

The value of this property is a comma-separated list of texture types that can be uploaded. Currently, the texture types are skin and cape.

For example, if the value of the uploadableTextures property is skin, it means the role can upload a skin but not a cape. If the value is skin,cape, it means both skins and capes can be uploaded.

If the uploadableTextures property is absent, no texture types can be uploaded for the role.

For more details about the texture upload interface, refer to §Texture Upload.

Texture URL Specification

Minecraft uses a texture hash as the identifier for the texture. Once the client downloads a texture, it will cache it locally. If the same hash is needed again, the client will directly use the cached version. However, this hash is not calculated by the client. The Yggdrasil server should calculate the texture hash first and use it as the filename in the texture URL, i.e., the substring from the last / (excluding it) to the end of the URL. The client will directly use the filename of the URL as the texture hash.

For example, the texture URL below represents a texture with the hash e051c27e803ba15de78a1d1e83491411dffb6d7fd2886da0a6c34a2161f7ca99:

https://yggdrasil.example.com/textures/e051c27e803ba15de78a1d1e83491411dffb6d7fd2886da0a6c34a2161f7ca99

Security Warning:

  • The Content-Type in the texture URL response header must be image/png. If not specified, there is a risk of MIME Sniffing Attack.

The method for calculating the texture hash is up to the server, but it is recommended to use SHA-256 or a more secure hash algorithm. As a reference, Mojang's official method is to calculate the SHA-256 of the image file.

Security of User-Uploaded Textures

Security Warning:

  • If user-uploaded textures are not properly handled, it could lead to remote code execution.
  • If the image size is not checked before reading, it could result in a denial-of-service attack.

For more details on this security flaw: Unchecked user-uploaded textures could lead to remote code execution #10

In addition to bitmap data, PNG files can store other data. If the Yggdrasil server does not properly check user-uploaded textures, an attacker could hide malicious code inside, which would be distributed to the client via the Yggdrasil server. Therefore, the Yggdrasil server must process user-uploaded textures, removing any data unrelated to the bitmap. The specific steps are as follows:

  1. Read the image size in the PNG file. If it's too large, it should be rejected.

    • Even very small PNG files can store images that consume all the computer's memory (i.e., a PNG Bomb), so the image should not be fully read before checking the size.
  2. Check if the image is a valid skin/cape texture.

    • The skin's width and height should be multiples of 64x32 or 64x64, and the cape's width and height should be multiples of 64x32 or 22x17. Capes with a width/height ratio of 22x17 are non-standard and should be padded with transparent pixels to 64x32.
  3. Re-save the image file, removing any unrelated metadata.

Implementation Tip: In Java, you can use ImageReader.getWidth() to get the image size without fully reading the image.

Token

A token has a many-to-one relationship with an account. It serves as a login credential and has an expiration time. A token has the following attributes:

  • accessToken
  • clientToken
  • Bound role
  • Issuance time

Where accessToken and clientToken are arbitrary strings (they can be unsigned UUIDs or JWTs). The accessToken is randomly generated by the server, while the clientToken is provided by the client.

Due to the randomness of the accessToken, it can be used as the primary key, while clientToken is not unique.

The bound role may be empty. It represents the character that can use this token to play the game.

A user can have multiple tokens, but the server should limit the number of tokens. When the number exceeds the limit (e.g., 10 tokens), the oldest token should be revoked first before issuing a new token.

Token States

A token can be in one of three states:

  • Active

  • Temporarily Inactive

    • Tokens in this state cannot perform any operations other than refreshing.
    • When the character bound to a token changes its name, the token should be marked as temporarily inactive.

      • This is to allow the launcher to refresh the token and obtain the new character name. (See #40)
    • This state is not mandatory to implement (detailed explanation below).
  • Invalid

    • Tokens in this state cannot perform any operations.
    • Tokens are invalidated when revoked. This includes explicit revocation, logout, refresh leading to revocation of the original token, or token expiration.

The state of a token can only change from active to invalid, or from active to temporarily inactive and then invalid. This process is irreversible. The refresh operation only issues a new token and does not return the original token to the active state.

Tokens should have an expiration limit (e.g., 15 days). If the time elapsed since issuance exceeds this limit, the token expires.

About Temporarily Inactive State

Mojang implements the temporarily inactive state as follows: For the launcher, if a token is in a temporarily inactive state, the launcher will refresh the token and obtain a new one in an active state. For the Yggdrasil server, only the most recently issued token is considered active, while earlier issued tokens are temporarily inactive.

Mojang's reasoning behind this is likely to prevent users from logging in simultaneously on multiple devices (i.e., only the last session remains valid). However, even without implementing a temporarily inactive state, the launcher's logic will work fine.

If we want to implement a temporarily inactive state, we don't have to follow Mojang's implementation exactly. As long as the launcher can handle it correctly, any implementation is acceptable. Here's an alternative example that differs from Mojang's approach:

Use a time segment shorter than the token expiration limit to separate the active and temporarily inactive states. If the time elapsed since issuance is within this segment, the token is active; if it exceeds this segment but is still within the expiration limit, the token becomes temporarily inactive.

This approach achieves the following functionality: if a player frequently logs in, they won't need to re-enter the password after the first login. However, if they haven't logged in for a long time, they will need to re-enter their password.


Yggdrasil API

User Section

Login

POST /authserver/authenticate

Authenticate using a password and assign a new token.

Request format:

{
    "username":"Email (or other credentials, see § Login using Character Name)",
    "password":"Password",
    "clientToken":"Client-specified token's clientToken (optional)",
    "requestUser":true/false, // Whether to include user information in the response, default is false
    "agent":{
        "name":"Minecraft",
        "version":1
    }
}

If the request does not include a clientToken, the server should randomly generate an unsigned UUID as the clientToken. However, note that clientToken can be any string, meaning any clientToken provided in the request is acceptable, not necessarily an unsigned UUID.

For the character that the token is bound to: If the user has no characters, it will be empty; if the user has only one character, it is usually bound to that character; if the user has multiple characters, it will typically be empty so the client can choose. In other words, if the bound character is empty, the client should prompt the user to choose a character.

Response format:

{
    "accessToken":"The token's accessToken",
    "clientToken":"The token's clientToken",
    "availableProfiles":[ // List of characters available to the user
        // ,... Each item is a character (format as described in § Serialization of Role Information)
    ],
    "selectedProfile":{
        // ... The bound character, if empty, it doesn't need to be included (format as described in § Serialization of Role Information)
    },
    "user":{
        // ... User information (only included when requestUser is true, format as described in § Serialization of User Information)
    }
}

Security Tip: This API can be used for brute-force password attacks and should be rate-limited. The rate limit should be based on the user, not the client's IP address.

Login Using Character Name

In addition to logging in with an email, the authentication server can also allow users to log in using their character name. To implement this, the authentication server needs to do the following:

When the user logs in using a character name, the authentication server should automatically bind the token to the corresponding character. In other words, the selectedProfile in the response should be the character used for login.

In this case, if the user has multiple characters, they can skip the character selection step. This method also helps bypass character selection for some programs that don't support multiple characters (e.g., Geyser).

Refresh

POST /authserver/refresh

Revokes the original token and issues a new one.

Request format:

{
    "accessToken":"The accessToken of the token",
    "clientToken":"The clientToken of the token (optional)",
    "requestUser":true/false, // Whether to include user information in the response, default is false
    "selectedProfile":{
        // ... The character to select (optional, format as described in § Serialization of Role Information)
    }
}

When clientToken is specified, the server should check if both the accessToken and clientToken are valid. Otherwise, it only needs to check the accessToken.

The clientToken of the newly issued token should be the same as the original token.

If the request contains selectedProfile, this indicates a character selection operation. In this case, the original token's bound character must be empty, and the new token will be bound to the character specified in selectedProfile. If selectedProfile is not included, the new token will be bound to the same character as the original token.

The refresh operation can still be performed even if the token is temporarily inactive. If the request fails, the original token remains valid.

Response format:

{
    "accessToken":"The new token's accessToken",
    "clientToken":"The new token's clientToken",
    "selectedProfile":{
        // ... The character bound to the new token, if empty, it doesn't need to be included (format as described in § Serialization of Role Information)
    },
    "user":{
        // ... User information (only included if requestUser is true, format as described in § Serialization of User Information)
    }
}

Validate Token

POST /authserver/validate

Checks whether a token is valid.

Request format:

{
    "accessToken":"The accessToken of the token",
    "clientToken":"The clientToken of the token (optional)"
}

When clientToken is specified, the server should check if both the accessToken and clientToken are valid. Otherwise, it only needs to check the accessToken.

If the token is valid, the server should return an HTTP status of 204 No Content. Otherwise, it should handle it as an invalid token.


Revoke Token

POST /authserver/invalidate

Revokes the given token.

Request format:

{
    "accessToken":"The accessToken of the token",
    "clientToken":"The clientToken of the token (optional)"
}

The server only needs to check the accessToken, meaning the value of clientToken does not affect the operation.

Regardless of whether the operation is successful, the server should return an HTTP status of 204 No Content.


Logout

POST /authserver/signout

Revokes all tokens of a user.

Request format:

{
    "username":"Email",
    "password":"Password"
}

If the operation is successful, the server should return an HTTP status of 204 No Content.

Security Tip: This API can also be used to check the correctness of the password, so it should be rate-limited in the same way as the login API.


Session Section

The diagram above is created using ProcessOn and exported as an SVG. Original image

This section handles the verification process when a character enters the server. The main flow is as follows:

  1. The Minecraft server and Minecraft client jointly generate a string (serverId), which can be considered random.
  2. The Minecraft client sends the serverId and the token to the Yggdrasil server (requiring the token to be valid).
  3. The Minecraft server requests the Yggdrasil server to check the validity of the client session, i.e., whether the client successfully completed step 2.

Client Joining the Server

POST /sessionserver/session/minecraft/join

Records the serverId sent by the server to the client for server verification.

Request format:

{
    "accessToken":"The accessToken of the token",
    "selectedProfile":"The UUID (unsigned) of the character bound to the token",
    "serverId":"The serverId sent to the client by the server"
}

The operation is successful only if the accessToken is valid, and the selectedProfile matches the character bound to the token.

The server should record the following information:

  • serverId
  • accessToken
  • The IP address of the client that sent the request

In implementation, this information should be stored in an in-memory database (like Redis), and an expiration time (e.g., 30 seconds) should be set. Due to the randomness of serverId, it can be used as the primary key.

If the operation is successful, the server should return an HTTP status of 204 No Content.


Server Verifying the Client

GET /sessionserver/session/minecraft/hasJoined?username={username}&serverId={serverId}&ip={ip}

Checks the validity of the client session, i.e., whether there is a record for the given serverId in the database, and if the information is correct.

Request parameters:

Parameter Value
username The character's name
serverId The serverId sent by the server to the client
ip (optional) The IP address of the client obtained by the Minecraft server. This is only included if the prevent-proxy-connections option is enabled in the server properties.

The username must match the character name bound to the token corresponding to the serverId.

Response format:

{
    // ... Complete information for the character bound to the token (including character properties and digital signature, format as described in § Serialization of Role Information)
}

If the operation fails, the server should return an HTTP status of 204 No Content.


Role Section

This section handles the querying of character information.

Query Character Properties

GET /sessionserver/session/minecraft/profile/{uuid}?unsigned={unsigned}

Queries the complete information of a specified character (including character properties).

Request parameters:

Parameter Value
uuid The UUID (unsigned) of the character
unsigned (optional) true or false. Whether to exclude the digital signature in the response. The default is true.

Response format:

{
    // ... Character information (including character properties. If unsigned is false, the digital signature must also be included. Format as described in § Serialization of Role Information)
}

If the character does not exist, the server should return an HTTP status of 204 No Content.


Batch Query Characters by Name

POST /api/profiles/minecraft

Performs a batch query for the characters corresponding to the given character names.

Request format:

[
    "CharacterName"
    // ,... Can include more character names
]

The server queries the character information corresponding to each character name and includes it in the response. Characters that do not exist do not need to be included. The order of characters in the response is not required.

Response format:

[
    {
        // Character information (note: does not include character properties. Format as described in § Serialization of Role Information)
    }
    // ,... (Can include more)
]

Security Tip: To prevent CC (credential stuffing) attacks, the number of characters that can be queried in a single request should be limited, with a minimum value of 2.

Texture Upload

PUT /api/user/profile/{uuid}/{textureType}
DELETE /api/user/profile/{uuid}/{textureType}

Set or clear a character’s texture.

Not all characters can upload skins and capes. To check what texture types a character is allowed to upload, see §uploadableTextures Uploadable Texture Types.

Request Parameters

Parameter Description
uuid Character's UUID (unsigned)
textureType Texture type, can be skin or cape

Requests must include the HTTP header Authorization: Bearer {accessToken} for authentication. If this header is missing or the accessToken is invalid, respond with 401 Unauthorized.

If the operation succeeds, respond with 204 No Content.


PUT – Upload Texture

The request must use Content-Type: multipart/form-data, with the payload consisting of the following parts:

Name Content
model (Skin only) The skin model: slim for slim arms, or an empty string for the default model.
file The texture image. Must be a PNG (Content-Type: image/png).
It is recommended that clients set the filename parameter in Content-Disposition to give the image a recognizable filename. The auth server can use this for texture comments or metadata.

If the operation succeeds, respond with 204 No Content.


DELETE – Clear Texture

Clears the specified texture type (skin or cape). The texture will be reset to default.


Extended API

These APIs are designed to assist with automatic configuration by tools such as authlib-injector.


API Metadata Retrieval

GET /

Returns metadata about the authentication server.

Response Format

{
    "meta": {
        // Arbitrary metadata about the server
    },
    "skinDomains": [ // Texture domain whitelist
        "domain-rule-1"
        // ,...
    ],
    "signaturePublickey": "PEM-formatted public key"
}
  • signaturePublickey is a PEM-formatted public key for verifying the digital signatures of character properties.

    • It starts with -----BEGIN PUBLIC KEY----- and ends with -----END PUBLIC KEY-----
    • Line breaks are allowed in the middle, but no other whitespace characters are permitted.
    • A trailing newline is allowed.

Texture Domain Whitelist

Minecraft will only download textures from whitelisted domains. If a texture URL's domain is not in the whitelist, the client will show the error:

Textures payload has been tampered with (non-whitelisted domain)

See MC-78491 for the reason behind this mechanism.

By default, the whitelist includes:

  • .minecraft.net
  • .mojang.com

You can add more via the skinDomains field. Rules:

  • If a rule starts with ., it matches all subdomains ending with the given string.

    • Example: .example.com matches a.example.com and b.a.example.com, but not example.com.
  • If a rule does not start with ., it must match the domain exactly.

    • Example: example.com matches example.com, but not a.example.com or sub.example.com.

meta Field – Server Metadata

The contents of meta are optional and arbitrary, but the following fields are commonly used:

Basic Server Info

Key Value
serverName Name of the server
implementationName Name of the auth server implementation
implementationVersion Version of the implementation

To display useful links in the launcher UI, add a links field to meta.

"links": {
    "homepage": "https://example.com",
    "register": "https://example.com/register"
}
Key Description
homepage Homepage URL of the auth server
register URL to user registration page

Feature Flags in meta

Fields marked with (advanced) are for advanced usage. In most cases, you don’t need to set them.

Key Description
feature.non_email_login Boolean. Indicates if the server supports login using credentials other than email (e.g., username). Default: false.
See §Login with Character Name.
feature.legacy_skin_api (advanced) Boolean. Indicates if the server supports Mojang's legacy skin API: GET /skins/MinecraftSkins/{username}.png.
If not set or false, authlib-injector will emulate it locally. If true, the auth server must handle the request.
See -Dauthlibinjector.legacySkinPolyfill in [README § Parameters].
feature.no_mojang_namespace (advanced) Boolean. Disables Mojang namespace (@mojang) feature in authlib-injector. Default: false.
See -Dauthlibinjector.mojangNamespace.
feature.enable_mojang_anti_features (advanced) Boolean. Enables Minecraft's “anti-features”. Default: false.
See -Dauthlibinjector.mojangAntiFeatures.
feature.enable_profile_key (advanced) Boolean. Enables chat message signing (profile key feature). Default: false.
If enabled, Minecraft will call POST /minecraftservices/player/certificates to obtain a key pair from the auth server. If disabled, Minecraft won't include signatures in chat messages.
See -Dauthlibinjector.profileKey.
feature.username_check (advanced) Boolean. Enables strict username validation. Default: false.
If enabled, players with special characters in their usernames will be rejected.
See -Dauthlibinjector.usernameCheck.

Here is the English translation of the provided section:


Example Response

{
    "meta": {
        "implementationName": "yggdrasil-mock-server",
        "implementationVersion": "0.0.1",
        "serverName": "yushijinhun's Example Authentication Server",
        "links": {
            "homepage": "https://skin.example.com/",
            "register": "https://skin.example.com/register"
        },
        "feature.non_email_login": true
    },
    "skinDomains": [
        "example.com",
        ".example.com"
    ],
    "signaturePublickey": "-----BEGIN PUBLIC KEY-----\nMIICIj... (omitted) ...EAAQ==\n-----END PUBLIC KEY-----\n"
}

API Location Indication (ALI)

API Location Indication (ALI) is an HTTP response header field named X-Authlib-Injector-API-Location, used for service discovery. The value of ALI is a relative or absolute URL that points to the Yggdrasil API associated with the current page.

With ALI, users only need to input an address associated with the Yggdrasil API, without needing to know the actual API endpoint. For example, https://skin.example.com/api/yggdrasil/ can be simplified to just skin.example.com. Launchers that support ALI will request (https://)skin.example.com, detect the ALI header in the response, and use it to find the real API endpoint.

A skin server can enable ALI on its homepage or across the entire site. To enable ALI, simply add the X-Authlib-Injector-API-Location header in HTTP responses, for example:

X-Authlib-Injector-API-Location: /api/yggdrasil/  # using a relative URL
X-Authlib-Injector-API-Location: https://skin.example.com/api/yggdrasil/  # or an absolute URL, cross-origin supported

If a page's ALI points to itself, the ALI will be ignored.


See Also


Reference Implementation

yggdrasil-mock is the reference implementation of this specification.