# Server Specifications
This article aims to provide unofficial technical specifications for implementing the Yggdrasil server.
The behavior of the server described in this specification is not necessarily the same as that of the Mojang server. This is objectively because Mojang's server is closed-source, and we can only speculate on its internal logic, and the speculation will inevitably differ from the actual existence. But in fact, as long as the client understands and handles the server's response correctly, it doesn't matter whether it behaves the same as the Mojang server.
# Basic conventions
# Character Encoding
The character encoding in this article will always use UTF-8.
# request and response format
Unless otherwise specified, requests and responses are in JSON format (if there is a body), and Content-Type
is both application/json; charset=utf-8
.
All APIs should use the HTTPS protocol.
# Error message format
{
"error":"A brief description of the error (machine readable)",
"errorMessage": "Error details (human readable)",
"cause":"The reason for the error (optional)"
}
When encountering the exceptions described in this article, the returned error message should meet the corresponding requirements.
The following table lists error messages for common exceptions. Unless otherwise specified, cause
is generally not included.
Non-standard means that since Mojang's Yggdrasil server cannot trigger the corresponding exception, you can only speculate the error message in this case.
Undefined means that the item is not explicitly required.
Exception | HTTP Status Code | Error | Error Message |
---|---|---|---|
General HTTP exceptions (non-business exceptions, such as Not Found, Method Not Allowed) | Undefined | The Reason Phrase corresponding to the HTTP status (in [HTTP/1.1](https://tools.ietf.org/ html/rfc2616#section-6.1.1)) | undefined |
Invalid token | 403 | ForbiddenOperationException | Invalid token. |
The password is incorrect, or the login is temporarily prohibited due to multiple login failures in a short period of time | 403 | ForbiddenOperationException | Invalid credentials. Invalid username or password. |
Attempting to assign a role to a token already bound to a role | 400 | IllegalArgumentException | Access token already has a profile assigned. |
Attempting to bind a token to a role that does not belong to its corresponding user (non-standard) | 403 | ForbiddenOperationException | undefined |
Attempted to join the server with an incorrect role | 403 | ForbiddenOperationException | Invalid token. |
# Data Format
We agree on the following data formats
- Unsigned UUID: refers to the UUID string with all
-
characters removed
# Model
# User
There can be several users in a system, and users have the following attributes: *ID
- password
where ID is an unsigned UUID. The email address can be changed, but it needs to be unique.
# Serialization of user information
The serialized user information conforms to the following format:
{
"id":"User ID",
"properties":[ // user's properties (array, each element is a property)
{ // an attribute
"name":"Property name",
"value":"Attribute value",
}
// ,... (there can be more)
]
}
The currently known items in user properties are as follows:
name | value |
---|---|
preferredLanguage | (optional) User's preferred language, e.g. en , zh_CN |
# Role (Profile)
Mojang currently does not support multi-role, and does not guarantee the correctness of the content of multi-role.
There is a many-to-one relationship between roles and accounts. A character corresponds to a physical player in Minecraft. A role has the following properties:
- UUID
- name
- Material model, optional values are:
default
,slim
- default: skin for normal arm width (4px)
- slim: skin for thin arms (3px)
- Material
- type is map
- key optional values are: SKIN, CAPE
- value type is URL
Both UUIDs and names are globally unique, but names are variable. The use of names as identifiers should be avoided.
# Character UUID generation
Regardless of compatibility, character UUIDs are generally randomly generated (Version 4 (opens new window)).
But Minecraft only uses UUIDs as character identifiers, and characters with different UUIDs are considered different even if they have the same name. If a Minecraft server is migrated to this login system from another login system (authentic, offline, or otherwise) and a character's UUID changes, the character's data will be lost. To avoid this, it must be guaranteed that for the same role, the UUID generated by this system is the same as the UUID in the previous system.
# Compatible with offline verification
If the Minecraft server originally used offline authentication, the character UUID is a unary function of the character name. If the Yggdrasil server uses this method to generate the character UUID, it can achieve two-way compatibility with the offline authentication system, that is, it can switch between the offline authentication system and this login system without losing character data.
The code to calculate the role UUID from the role name is as follows (Java):
UUID.nameUUIDFromBytes(("OfflinePlayer:" + characterName).getBytes(StandardCharsets.UTF_8))
Implementations in other languages:
# Serialization of role information
The serialized role information conforms to the following format:
{
"id":"Role UUID (unsigned)",
"name":"Character name",
"properties":[ // properties of the role (array, one property per element) (only required in certain cases)
{ // an attribute
"name":"Property name",
"value":"Attribute value",
"signature":"Digital signature of attribute value (only required in certain cases)"
}
// ,... (there can be more)
]
}
Role properties (properties
) and digital signatures (signature
) do not need to be included unless otherwise specified.
signature
is the digital signature of the property value, encoded in Base64. The signature algorithm is SHA1withRSA, see PKCS #1 (opens new window). For details on signing keys, see [Signing Key Pair](Signing Key Pair).
The following items can be included in role attributes:
name | value |
---|---|
textures | (optional) Base64 encoded JSON string containing the character's texture information, see [§textures texture information properties](#textures-texture information properties). |
uploadableTextures | (optional) The type of textures that the character can upload, which is a property specified by authlib-injector, see [§uploadableTextures uploadable texture types](#uploadableTextures-uploadable texture types) |
# textures
material information properties
The following is the format of the material information. After Base64 encoding this JSON, it is the value of the textures
character attribute.
{
"timestamp": The timestamp when the property value was generated (Java timestamp format, i.e. the number of milliseconds elapsed since 1970-01-01 00:00:00 UTC),
"profileId":"Role UUID (unsigned)",
"profileName":"Role Name",
"textures":{ // textures of the character
"Material Type (eg SKIN)":{ // If the character does not have this material, it does not have to be included
"url":"URL of the material",
"metadata":{ // The metadata of the material, if there is none, it does not have to be included
"name":"value"
// ,... (there can be more)
}
}
// ,... (there can be more)
}
}
The currently known items in the material metadata are model
, which corresponds to the material model of the character, with a value of default
or slim
.
# uploadableTextures
uploadable texture types
Note: This role attribute is specified by the authlib-injector documentation, and the role attribute returned by Mojang does not include this item. Mojang only allows users to upload skins, not capes.
Considering that not all authentication servers allow users to upload skins and capes, the authlib-injector specifies the uploadableTextures
character property, which represents the types of textures a character can upload.
The value of this property is a comma-separated list of the types of materials that can be uploaded. There are currently two types of materials, skin
and cape
.
For example, if the value of the uploadableTextures
property is skin
, it means that skins can be uploaded for the character, but capes cannot be uploaded; if the value is skin,cape
, both skins and capes can be uploaded.
If the uploadableTextures
property is not present, you cannot upload any type of texture for the character.
For the introduction of the material upload interface, please refer to [§Material upload](#material upload).
# Material URL Specification
Minecraft uses the texture hash as the texture's identifier. Whenever the client downloads a material, it will be cached locally. If a material with the same hash is needed in the future, the cache will be used directly.
And this hash is not calculated by the client. The Yggdrasil server should first calculate the material hash and use it as the file name of the material URL, that is, the substring starting from the last /
(excluding) of the URL to the end.
The client will directly use the filename of the URL as the hash of the material.
For example, the following URL, the hash of the material it represents is e051c27e803ba15de78a1d1e83491411dffb6d7fd2886da0a6c34a2161f7ca99
:
https://yggdrasil.example.com/textures/e051c27e803ba15de78a1d1e83491411dffb6d7fd2886da0a6c34a2161f7ca99
Safety Warning:
- The
Content-Type
in the material URL response header must beimage/png
. If not specified, there is a risk of MIME Sniffing Attack.
The server can choose the calculation method of the material hash. It is recommended to use SHA-256 or a more secure hash algorithm. For reference, Mojang's official method is to calculate the SHA-256 of an image file.
# User-uploaded material security
Safety Warning:
- may result in remote code execution if user-uploaded materials are not processed.
- may lead to a denial of service attack if the image size is not checked before reading the material
Details about this security flaw: Unchecked user-uploaded material could lead to remote code execution #10 (opens new window)
In addition to bitmap data, PNG files can store other data. If the Yggdrasil server does not check the materials uploaded by the user, attackers can hide malicious code in it and distribute it to the client through the Yggdrasil server. Therefore, the Yggdrasil server must process user-uploaded materials, removing any data that is not related to bitmaps. The specific methods are as follows:
- Read the size of the image in this PNG file and reject if it is too large.
- Even a very small PNG file can store an image that can consume all of your computer's memory (i.e. a PNG Bomb), so never read the image in its entirety before checking its size.
- Check that the image is a legal skin/cloak texture.
- The width and height of the skin is an integral multiple of 64x32 or an integral multiple of 64x64, and the width and height of the cape are an integral multiple of 64x32 or an integral multiple of 22x17. A cloak whose width and height are an integer multiple of 22x17 is not a standard size cloak. The server needs to use transparent pixels to make up its width and height to an integer multiple of 64x32.
- Re-save the image file to remove extraneous metadata.
Implementation hint: In Java you can use
ImageReader.getWidth()
(opens new window) Get its dimensions without reading in the entire image.
# Token
There is a many-to-one relationship between tokens and accounts. A token is a login credential and is time-sensitive. Tokens have the following properties:
- accessToken
- clientToken
- bound character
- Issue time
where accessToken
and clientToken
are arbitrary strings (can be unsigned UUIDs or JWTs). accessToken
is randomly generated by the server, and clientToken
is provided by the client.
Between the randomness of accessToken
, it can be used as the primary key. And clientToken
is not unique.
The bound role can be empty. It represents a character that can play with the token.
A user can have multiple tokens at the same time, but the server should also limit the number of tokens. When the number of tokens exceeds the limit (eg 10), the oldest token should be revoked before a new token is issued.
# Status of the token
A token has the following three states:
- efficient
- Temporarily invalid
- The token in this state has no right to perform any operation except refresh operation.
- When the role to which the token is bound is renamed, the token should be marked as temporarily invalid.
- This is for the initiator to refresh the token to get the new role name. (See #40 (opens new window))
- _This state is not required to be implemented (see below for details). _
- Invalid
- Tokens in this state are not authorized to do anything.
- In this state after the token has been revoked. The revocation here includes [explicit revocation] (#revoke token), [logout] (#logout), [refresh] (#refresh) after the original token is revoked, and the token expires.
The state of the token can only be changed from valid to invalid, or from valid to temporarily invalid and then invalid again. This process is irreversible. The refresh operation only issues a new token and does not return the original token to a valid state.
Tokens should have an expiration time (eg 15 days). The token expires when the time elapsed since it was issued exceeds this time limit.
# About temporary invalid status
Mojang's implementation of the temporarily invalid state is as follows: For the initiator, if the token is temporarily invalid, the token will be refreshed and a new token in a valid state will be obtained; For the Yggdrasil server, only the last token issued is valid, other tokens issued previously are temporarily invalid.
Mojang probably does this to prevent multiple users from logging in at the same time (only the last session is valid). But in fact, even if the server does not implement a temporary failure state, the logic of the initiator can work normally.
Of course, even if we want to implement a temporary failure state, we don't need to use Mojang's implementation as a template. Any implementation is fine as long as the launcher can handle it correctly. Here is an example of an implementation different from Mojang:
Take a time period shorter than the token expiration time as the demarcation point between valid and temporary invalidation. If the time elapsed since issuance is within this time limit, the token is valid; if it exceeds this time limit, but is still within the expiration time limit, the token is temporarily invalid.
This approach achieves such a function: if players log in frequently, they do not need to enter a password except for the first login. And when he hasn't logged in for a long time, he needs to re-enter the password.
# Yggdrasil API
# user section
# Log in
POST /authserver/authenticate
Authenticate with a password and assign a new token.
Request format:
{
"username":"Email (or other credentials, see §Login with role name)",
"password":"password",
"clientToken":"clientToken of the token specified by the client (optional)",
"requestUser":true/false, // Whether to include user information in the response, default false
"agent":{
"name":"Minecraft",
"version":1
}
}
If clientToken
is not included in the request, the server SHOULD generate a random unsigned UUID as clientToken
. But it should be noted that clientToken
can be any string, i.e. any clientToken
is acceptable in the request, not necessarily an unsigned UUID.
For the role to be bound to the token: if the user does not have any role, it is empty; if the user has only one role, it is usually bound to this role; if the user has multiple roles, it is usually empty so that the client can choose . That is to say, if the bound role is empty, the client is required to select the role.
Response format:
{
"accessToken":"accessToken of the token",
"clientToken":"clientToken of the token",
"availableProfiles":[ // list of user available roles
// ,... Each item is a role (see §Serialization of role information for the format)
],
"selectedProfile":{
// ... bound role, if it is empty, it does not need to be included (see §Serialization of role information for the format)
},
"user":{
// ... user information (only included when requestUser is true in the request, see §Serialization of user information for the format)
}
}
SECURITY NOTE: This API can be used for password brute force cracking and should be rate limited. Restrictions should be on users, not client IPs.
# Login with role name
In addition to logging in with a mailbox, the authentication server can also allow users to log in with a role name. To achieve this, the authentication server needs to do the following:
- Set the
feature.non_email_login
field in the API metadata to true. (See [API Metadata Acquisition §Function Options](#Function Options)) - Accept role name as
username
parameter in login interface.
When a user logs in with a role name, the authentication server SHOULD automatically bind the token to the corresponding role, i.e. the selectedProfile
in the response above should be the role the user logged in with.
In this case, if the user has more than one role, he can save the operation of selecting the role. Considering that some programs do not support multiple roles (eg Geyser), it is also possible to bypass role selection by the above method.
# refresh
POST /authserver/refresh
Revoke the original token and issue a new one.
Request format:
{
"accessToken":"accessToken of the token",
"clientToken":"clientToken of the token (optional)",
"requestUser":true/false, // Whether to include user information in the response, default false
"selectedProfile":{
// ... the role to be selected (optional, see §Serialization of role information for the format)
}
}
When specifying clientToken
, the server should check whether accessToken
and clientToken
are valid, otherwise only need to check accessToken
.
The clientToken
of the new token issued should be the same as the original token.
If the request contains a selectedProfile
, then this is an operation that selects a role. This operation requires that the role bound to the original token is empty, and the new token will be bound to the role specified by selectedProfile
. If selectedProfile
is not included, the new token is bound to the same role as the original token.
The refresh operation can still be performed when the token is temporarily expired. If the request fails, the original token is still valid.
Response format:
{
"accessToken":"accessToken of the new token",
"clientToken":"clientToken of the new token",
"selectedProfile":{
// ... the role bound to the new token, if it is empty, it does not need to be included (see §Serialization of role information for the format)
},
"user":{
// ... user information (only included when requestUser is true in the request, see §Serialization of user information for the format)
}
}
# verify token
POST /authserver/validate
Verify that the token is valid.
Request format:
{
"accessToken":"accessToken of the token",
"clientToken": "clientToken of the token (optional)"
}
When specifying clientToken
, the server should check whether accessToken
and clientToken
are valid, otherwise only need to check accessToken
.
If the token is valid, the server should return the HTTP status 204 No Content
, otherwise it will be handled as an exception that the token is invalid.
# revoke token
POST /authserver/invalidate
Revoke the given token.
Request format:
{
"accessToken":"accessToken of the token",
"clientToken": "clientToken of the token (optional)"
}
The server only needs to check accessToken
, that is, no matter what the value of clientToken
is, it will not matter.
Whether the operation was successful or not, the server should return HTTP status 204 No Content
.
# Sign out
POST /authserver/signout
Revoke all tokens for the user.
Request format:
{
"username":"Email",
"password":"password"
}
If the operation is successful, the server should return HTTP status 204 No Content
.
SECURITY NOTE: This API can also be used to determine the correctness of passwords and should therefore be subject to the same rate limits as the login API.
# session section
![Minecraft player entry principle](https://raw.githubusercontent.com/wiki/yushijinhun/authlib-injector/mc server entry principle.svg?sanitize=true)
The picture above makes Draw with ProcessOn, export as SVG. Original image (opens new window)
This section is used for authentication when the role enters the server. The main process is as follows:
- Minecraft server and Minecraft client together generate a string (
serverId
), which can be considered random - Minecraft client sends
serverId
and token to Yggdrasil server (requires token to be valid) - Minecraft server requests Yggdrasil server to check the validity of the client session, that is, whether the client successfully performs step 2
# Client enters server
POST /sessionserver/session/minecraft/join
Record the serverId
sent by the server to the client for the server to check.
Request format:
{
"accessToken":"accessToken of the token",
"selectedProfile":"UUID (unsigned) of the role bound to this token",
"serverId": "serverId sent by the server to the client"
}
The operation succeeds only if accessToken
is valid and selectedProfile
matches the role to which the token is bound.
The server should record the following information:
- serverId
- accessToken
- The client IP that sent the request
Note when implementing: the above information should be recorded in an in-memory database (such as Redis), and an expiration time should be set (such as 30 seconds).
Between the randomness of serverId
, it can be used as the primary key.
If the operation is successful, the server should return HTTP status 204 No Content
.
# Server authentication client
GET /sessionserver/session/minecraft/hasJoined?username={username}&serverId={serverId}&ip={ip}
Check the validity of the client session, that is, whether there is a record for this serverId
in the database, and the information is correct.
Request parameters:
parameter | value |
---|---|
username | The name of the role |
serverId | serverId sent from the server to the client |
ip (optional) | The client IP obtained by the Minecraft server, only when prevent-proxy-connections (opens new window) option with |
username
needs to be the same as the name of the role to which the token corresponding to serverId
is bound.
Response format:
{
// ... the complete information of the role bound to the token (including role attributes and digital signatures, see §Serialization of role information for the format)
}
If the operation fails, the server should return HTTP status 204 No Content
.
# roles section
This part is used for query of role information.
# Query role attributes
GET /sessionserver/session/minecraft/profile/{uuid}?unsigned={unsigned}
Query the complete information of the specified role (including role attributes).
Request parameters:
parameter | value |
---|---|
uuid | UUID of the role (unsigned) |
unsigned (optional) | true or false . Whether to do not include the digital signature in the response, the default is true |
Response format:
{
// ... role information (including role attributes. If unsigned is false, it also needs to include a digital signature. For the format, see §Serialization of role information)
}
If the role does not exist, the server should return HTTP status 204 No Content
.
# Batch query roles by name
POST /api/profiles/minecraft
Batch query roles corresponding to role names.
Request format:
[
"Role Name"
// ,... and more
]
The server queries the role information corresponding to each role name and includes it in the response. Non-existent roles do not need to be included. The order of role information in the response is not required.
Response format:
[
{
// Role information (Note: does not contain role attributes. For the format, see §Serialization of role information)
}
// ,... (there can be more)
]
Security Tips: To prevent CC attacks, you need to set the maximum number of roles for a single query, the value should be at least 2.
# Material upload
PUT /api/user/profile/{uuid}/{textureType}
DELETE /api/user/profile/{uuid}/{textureType}
Sets or clears the material for the specified character.
Not all characters can upload skins and capes. To get the texture types that the current character can upload, see [§
uploadableTextures
uploadable texture types](#uploadableTextures-uploadable texture types).
Request parameters:
parameter | value |
---|---|
uuid | UUID of the role (unsigned) |
textureType | Texture type, can be skin (skin) or cape (cloak) |
The request needs to carry the HTTP header Authorization: Bearer {accessToken}
for authentication. Returns 401 Unauthorized
if the Authorization header is not included or the accessToken is invalid.
Returns 204 No Content
if the operation is successful.
The following describes the usage of the two HTTP methods PUT and DELETE:
# PUT upload material
The Content-Type
of the request is multipart/form-data
, and the request payload consists of the following parts:
name | content |
---|---|
model | (for skin only) The material model for the skin, which can be slim (slim arm skin) or an empty string (normal skin). |
file | Material image, Content-Type must be image/png . Clients are advised to set the filename parameter in Content-Disposition to the filename of the material image, which can be used by the authentication server as a note for the material. |
Returns 204 No Content
if the operation is successful.
# DELETE clear material
After clearing the material, the material of that type will revert to the default.
# Extension API
The following APIs are designed to facilitate automatic configuration by authlib-injector.
# API metadata acquisition
GET /
Response format:
{
"meta":{
// The metadata of the server, the content is arbitrary
},
"skinDomains":[ // Material domain whitelist
"Domain Match Rule 1"
// ,...
],
"signaturePublickey":"The public key used to verify the digital signature"
}
signaturePublickey
is a public key in PEM format used to verify the digital signature of the role attributes. It starts with -----BEGIN PUBLIC KEY-----
and ends with -----END PUBLIC KEY-----
, and newlines are allowed in the middle, but other whitespace characters are not allowed (A newline at the end of the text is also allowed).
# Material Domain Name Whitelist
Minecraft will only download textures from whitelisted domains. The Textures payload has been tampered with (non-whitelisted domain)
error will appear if the domain name of the texture URL is not in the whitelist. See MC-78491 (opens new window) for the reason for this mechanism.
The material whitelist contains two rules, .minecraft.net
and .mojang.com
by default. You can set the skinDomains
property to add additional whitelist rules. The rule format is as follows:
- If the rule starts with
.
(dot), matches domains ending with this rule.- For example
.example.com
matchesa.example.com
,b.a.example.com
, does not matchexample.com
.
- For example
- If the rule ** does not start with **
.
(dot), the matching domain name must be exactly as the rule.- For example
example.com
matchesexample.com
, does not matcha.example.com
,eexample.com
.
- For example
# Metadata in meta
The content in meta
is not mandatory, the following fields are optional.
# Server basic information
Key | Value |
---|---|
serverName | server name |
implementationName | Name of server implementation |
implementationVersion | Server implementation version |
# Server URL
If you need to display the authentication server home page address, registration page address and other information in the launcher, you can add a links
field in meta
.
The type of the links
field is an object, which can contain:
Key | Value |
---|---|
homepage | Verify the home page address of the server |
register | Register page address |
# Feature options
The fields marked with (advanced) below are advanced options, usually not required to be set.
Key | Value |
---|---|
feature.non_email_login | Boolean value indicating whether the authentication server supports login with credentials other than mailbox (such as role name login), defaults to false. See [§Login with role name](#Login with role name) for details. |
feature.legacy_skin_api | (advanced) Boolean indicating whether the authentication server supports the legacy skin API, i.e. GET /skins/MinecraftSkins/{username}.png . When not specified or the value is false, the authlib-injector will use the built-in HTTP server to handle the request to the API locally; if the value is true, the request will be handled by the authentication server. See the -Dauthlibinjector.legacySkinPolyfill option in README § Parameters (opens new window) for details. |
feature.no_mojang_namespace | (advanced) Boolean value, whether to disable the Mojang namespace (@mojang suffix) feature of authlib-injector, the default is false. See the -Dauthlibinjector.mojangNamespace option in README § Parameters (opens new window) for details. |
feature.enable_mojang_anti_features | (advanced) Boolean value, whether to enable anti-features in Minecraft, the default is false. See the -Dauthlibinjector.mojangAntiFeatures option in README § Parameters (opens new window) for details. |
feature.enable_profile_key | (advanced) Boolean value indicating whether the verification server supports Minecraft's message signing key pair feature |
Ability (i.e. digital signature of chat messages in multiplayer games), defaults to false. When enabled, Minecraft will obtain the key pair issued by the authentication server via the POST /minecraftservices/player/certificates API. When unspecified or false, Minecraft will not include digital signatures in chat messages. See the -Dauthlibinjector.profileKey option in README § Parameters (opens new window) for details. | |
feature.username_check | (advanced) Boolean indicating whether authlib-injector enables username verification, defaults to false. Players with special characters in their usernames will not be able to join the server when enabled, see the -Dauthlibinjector.usernameCheck option in README § Parameters (opens new window) for details. |
# Response example
{
"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 Address Indication (ALI)
API Location Indication (ALI) is an HTTP response header field X-Authlib-Injector-API-Location
, which plays the role of service discovery. The value of the ALI is a relative or absolute URL that points to the Yggdrasil API associated with the current page.
After using ALI, users only need to enter an address associated with the Yggdrasil API, instead of entering the real API address. For example, https://skin.example.com/api/yggdrasil/
can be simplified to skin.example.com
. Initiators that support ALI will request (https://)skin.example.com
, identify the ALI header field in the response, and use it to find the real API address.
The skin station can enable ALI on the homepage, or on the whole station. ALI is enabled by adding the X-Authlib-Injector-API-Location
header field to the HTTP response, for example:
X-Authlib-Injector-API-Location: /api/yggdrasil/ # Use relative URLs
X-Authlib-Injector-API-Location: https://skin.example.com/api/yggdrasil/ # Absolute URL can also be used to support cross-domain
When a page's ALI points to itself, the ALI is ignored.
# see
- Authentication - wiki.vg (opens new window)
- Mojang API - wiki.vg (opens new window)
- Protocol - wiki.vg (opens new window)
# reference implementation
yggdrasil-mock (opens new window) is the reference implementation of this specification.