The goals of this project:
Provide a unified login experience outside of Mojang's official system for players.
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 API for Blessing Skin
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.
The latest version of authlib-injector can be downloaded directly from authlib-injector.yushi.moe.
The authlib-injector project provides a set of APIs for downloading authlib-injector builds. The API endpoint is https://authlib-injector.yushi.moe/
.
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 /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 /artifact/latest.json
Response format is the same as above. Use this API if you only need to get the latest version.
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/
.
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.
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.
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:
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.
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.
To resolve the user-inputted address into the actual API address, the launcher must:
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.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
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.
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
.
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";
}
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.
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.
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.
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.
The launcher identifies an account by the following three immutable attributes:
Account identifier (e.g., email)
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)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:
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.
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:
selectedProfile
is not null, login is successful; update account attributes from the response and finish.availableProfiles
is empty, the user has no profiles; throw an exception.availableProfiles
.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:
If selectedProfile
is not null:
selectedProfile
matches the account’s profile UUID, update user attributes from the login response; finish.availableProfiles
. If none, throw an exception (original profile unavailable).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.
Before launching the game, the launcher needs to do the following:
Steps 1, 2, and 3 can be done in parallel to speed up launching.
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.
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.
The launcher must add the following JVM parameters (before the main class arguments):
javaagent parameter:
-javaagent:{path_to_authlib-injector.jar}={authentication_server_API_address}
Prefetch configuration:
-Dauthlibinjector.yggdrasil.prefetched={Base64_encoded_API_metadata}
Example using example.yggdrasil.yushi.moe:
/home/user/.launcher/authlib-injector.jar
.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)
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 {} ) |
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:
unsigned=false
)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.
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>
.
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.
openssl rsa -pubout
The private key is read from standard input, and the public key is output to standard output.
First, you need to download the latest version of authlib-injector from here.
Since authlib-injector v1.2.0, you need to set
enforce-secure-profile
totrue
. 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:
authlib-injector.jar
.minecraft_server.1.12.2.jar
.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
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.
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"}
/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.
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 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.
All characters in this document use UTF-8 encoding.
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":"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. |
We define the following data formats:
-
characters removed.A system can have several users, each with the following attributes:
The ID is an unsigned UUID. The email can be changed, but it must be unique.
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 |
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:
Skin model, optional values: default
, slim
Texture
Both the UUID and name are globally unique, but the name can change. It's recommended to avoid using the name as an identifier.
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.
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:
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 PropertyThe 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 TypesNote: 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.
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 beimage/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 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:
Read the image size in the PNG file. If it's too large, it should be rejected.
Check if the image is a valid skin/cape texture.
Implementation Tip: In Java, you can use
ImageReader.getWidth()
to get the image size without fully reading the image.
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
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.
A token can be in one of three states:
Active
Temporarily Inactive
When the character bound to a token changes its name, the token should be marked as temporarily inactive.
Invalid
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.
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.
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.
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:
feature.non_email_login
field in the API metadata to true
. (See API Metadata § Feature Options)username
parameter in the login interface.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).
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)
}
}
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.
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
.
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.
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:
serverId
), which can be considered random.serverId
and the token to the Yggdrasil server (requiring the token to be valid).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
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
.
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
.
This section handles the querying of character information.
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
.
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.
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.
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
.
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
.
Clears the specified texture type (skin or cape). The texture will be reset to default.
These APIs are designed to assist with automatic configuration by tools such as authlib-injector.
GET /
Returns metadata about the authentication server.
{
"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.
-----BEGIN PUBLIC KEY-----
and ends with -----END PUBLIC KEY-----
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.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.com
matches example.com
, but not a.example.com
or sub.example.com
.meta
Field – Server MetadataThe contents of meta
are optional and arbitrary, but the following fields are commonly used:
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 |
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:
{
"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) 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.
yggdrasil-mock is the reference implementation of this specification.