Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Yggdrasil Server Technical Specification

Overview

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

The server behaviors described in this specification are not necessarily identical to those of the Mojang server. This is because Mojang’s server is closed source; we can only speculate on its internal logic, and such speculations will inevitably deviate from reality. However, as long as the client can correctly understand and process the server’s response, whether its behavior is identical to the Mojang server is irrelevant.

Basic Conventions

Character Encoding

The character encoding used everywhere in this document is UTF-8.

Request and Response Format

Unless otherwise specified, requests and responses are in JSON format (if there is a body), and the Content-Type is always 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 error information (Human readable)",
	"cause": "The cause of the error (Optional)"
}

When encountering the exceptions described in this document, the returned error message should conform to the corresponding requirements.

The table below lists error messages for common exceptions. Unless otherwise specified, cause is generally not included.

Non-standard indicates that because it is impossible to trigger the corresponding exception on Mojang’s Yggdrasil server, the error message in such cases can only be speculated.

Undefined indicates there is no explicit requirement for this item.

Exception situationHTTP status codeErrorError message
General HTTP Exception (Non-business exception, e.g. Not Found, Method Not Allowed)UndefinedThe Reason Phrase corresponding to the HTTP status (defined in HTTP/1.1)Undefined
Invalid token403ForbiddenOperationExceptionInvalid token.
Wrong password, or login temporarily banned due to multiple failures in a short time403ForbiddenOperationExceptionInvalid credentials. Invalid username or password.
Attempting to assign a profile to a token that already has a profile assigned400IllegalArgumentExceptionAccess token already has a profile assigned.
Attempting to assign a profile not belonging to the corresponding user to a token (Non-standard)403ForbiddenOperationExceptionUndefined
Attempting to join a server using the wrong profile403ForbiddenOperationExceptionInvalid token.

Data Format

We agree on the following data formats:

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

Models

User

A system can contain multiple users. A user has the following attributes:

  • ID
  • Email
  • Password

Where ID is an unhyphenated UUID. The email can be changed, but must remain unique.

Serialization of User Information

User information conforms to the following format after serialization:

{
	"id": "User ID",
	"properties": [ // User attributes (Array, each element is an attribute)
		{ // An attribute
			"name": "Attribute Name",
			"value": "Attribute Value",
		}
		// ,... (Can have more)
	]
}

Known items in user attributes are as follows:

NameValue
preferredLanguage(Optional) User’s preferred language, e.g. en, zh_CN

Profile

Mojang currently does not support multiple profiles; so the documentation here may not match Mojang’s implementation.

Profiles and accounts have a many-to-one relationship. A profile corresponds to a player entity in Minecraft. A profile has the following attributes:

  • UUID
  • Name
  • Texture model (either default or slim)
    • default: Skin with normal arm width (4px)
    • slim: Skin with thin arms (3px)
  • Textures
    • Represented as a map whose keys are either "SKIN" or "CAPE" and whose values are URLs

The UUID and Name are globally unique, but the Name is mutable. Use of the Name as an identifier should be avoided.

Profile UUID Generation

If compatibility is not considered, the profile UUID is generally randomly generated (Version 4).

However, Minecraft uses only UUIDs to identify profiles; profiles with different UUIDs are considered different even if they have the same name. If a Minecraft server migrates from another login system (Online verification, Offline verification, or others) to this login system, and the profile’s UUID changes, the profile’s data will be lost. To avoid this, it must be guaranteed that for the same profile, the UUID generated by this system is the same as the UUID in the previous system.

Compatibility with Offline Verification

If the Minecraft server previously used offline verification, the profile UUID is a pure function of the profile name. If the Yggdrasil server uses this method to generate profile UUIDs, bidirectional compatibility with the offline verification system can be achieved, i.e. the server can switch between the offline verification system and this login system without losing profile data.

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

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

Implementations in other languages:

Serialization of Profile Information

Profile information conforms to the following format after serialization:

{
	"id": "Profile UUID (Unhyphenated)",
	"name": "Profile Name",
	"properties": [ // Profile attributes (Array, each element is an attribute) (Only needed in specific cases)
		{ // An attribute
			"name": "Attribute Name",
			"value": "Attribute Value",
			"signature": "Digital signature of the attribute value (Only needed in specific cases)"
		}
		// ,... (Can have more)
	]
}

Profile attributes (properties) and digital signatures (signature) do not need to be included unless otherwise specified.

signature is the digital signature of the attribute value, encoded as Base64. The signature algorithm is SHA1withRSA; see PKCS #1. For details on the signing key, see Signature Key Pair.

Profile attributes can contain the following items:

NameValue
textures(Optional) Base64-encoded JSON string containing the profile’s texture information. See §textures Texture Information Attribute.
uploadableTextures(Optional) Texture types that this profile can upload. This is an attribute defined by authlib-injector. See §uploadableTextures Uploadable Texture Types
textures Texture Information Attribute

The following is the format of texture information. After Base64-encoding this JSON, it becomes the value of the textures profile attribute.

{
	"timestamp": Timestamp when this attribute value was generated (Java timestamp format, i.e., milliseconds elapsed since 1970-01-01 00:00:00 UTC),
	"profileId": "Profile UUID (Unhyphenated)",
	"profileName": "Profile Name",
	"textures": { // Profile textures
		"Texture Type (e.g., SKIN)": { // If the profile does not have this texture, it need not be included
			"url": "Texture URL",
			"metadata": { // Texture metadata, if none, it need not be included
				"Name": "Value"
				// ,... (Can have more)
			}
		}
		// ,... (Can have more)
	}
}

Currently known items in texture metadata include model, corresponding to the profile’s texture model, with values default or slim.

uploadableTextures Uploadable Texture Types

Note: This profile attribute is defined by the authlib-injector documentation. Profile attributes returned by Mojang do 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, authlib-injector defines the uploadableTextures profile attribute, which indicates the types of textures the profile can upload.

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

For example, if the value of the uploadableTextures attribute is skin, it means a skin can be uploaded for this profile, but not a cape; if the value is skin,cape, then both a skin and a cape can be uploaded.

If the uploadableTextures attribute does not exist, no type of texture can be uploaded for this profile.

For an introduction to the texture upload interface, please refer to §Texture Upload.

Texture URL Specification

Minecraft uses the texture hash as the identifier for the texture. Whenever the client downloads a texture, it caches it locally. If a texture with the same hash is needed later, the cache will be used directly. This hash is not calculated by the client. The Yggdrasil server should calculate the texture hash in advance and use it as the filename in the texture URL, i.e., the substring starting from after the last / in the URL to the end. The client will directly use the URL’s filename as the texture’s hash.

For example, in the following URL, the hash of the texture it represents is e051c27e803ba15de78a1d1e83491411dffb6d7fd2886da0a6c34a2161f7ca99:

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

Warning

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

The server can choose the method for calculating the texture hash; 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

Warning

  • Failure to process user-uploaded textures may lead to Remote Code Execution.
  • Failure to check image size before reading the texture may lead to Denial of Service attacks.

For detailed information on this security flaw: Unchecked user uploaded textures may lead to remote code execution #10

In addition to bitmap data, PNG files can store other data. If the Yggdrasil server does not check user-uploaded textures, attackers can hide malicious code within them and distribute it to clients via the Yggdrasil server. Therefore, the Yggdrasil server must process user-uploaded textures to remove any data unrelated to the bitmap. Specific steps are as follows:

  1. Read the size of the image in the PNG file; if it is too large, it should be rejected.
    • Even a very small PNG file can store an image large enough to consume all of a computer’s memory (i.e., a PNG Bomb), so never read the entire file before checking the image size.
  2. Check if the image is a valid skin/cape texture.
    • Skin dimensions are multiples of 64x32 or 64x64; cape dimensions are multiples of 64x32 or 22x17. Capes with dimensions that are multiples of 22x17 are not standard-sized capes; the server needs to pad their width and height with transparent pixels to a multiple of 64x32.
  3. Re-save the image file, removing irrelevant metadata.

Implementation Hint: In Java, use ImageReader.getWidth() to get the dimensions without reading the entire image.

Token

Tokens and accounts have a many-to-one relationship. A token is a login credential and has a validity period. Tokens have the following attributes:

  • accessToken
  • clientToken
  • Bound Profile
  • Time of Issuance

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

Given the randomness of accessToken, it can serve as a primary key. clientToken is not unique.

The bound profile can be null. It represents the profile that can use this token to play the game.

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

Token States

Tokens have the following three states:

  • Valid
  • Temporarily Invalid
    • A token in this state has no right to perform any operation except for the Refresh operation.
    • When the name of the profile bound to the token changes, the token should be marked as Temporarily Invalid.
      • This is to allow the launcher to refresh the token, thereby obtaining the new profile name. (See #40)
    • This state is not mandatory to implement (see below for details).
  • Invalid
    • A token in this state has no right to perform any operation.
    • A token is in this state after being revoked. Revocation here includes Explicit Revocation, Signout, revocation of the original token after Refresh, and token expiration.

Token state can only change from valid to invalid, or from valid to temporarily invalid and then to invalid; 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 limit (e.g., 15 days). When the time elapsed since issuance exceeds this limit, the token expires.

About Temporarily Invalid State

Mojang’s implementation of the temporarily invalid state is as follows: For the launcher, if a token is in a temporarily invalid state, it will refresh the token to obtain a new valid token. For the Yggdrasil server, only the last issued token is valid; other previously issued tokens are in a temporarily invalid state.

Mojang likely does this to prevent users from logging in from multiple locations simultaneously (making only the last session valid). However, in fact, even if the server does not implement the temporarily invalid state, the launcher’s logic works correctly.

Of course, even if we want to implement the temporarily invalid state, we do not need to model it after Mojang’s implementation. Any implementation is acceptable as long as the launcher can handle it correctly. Here is an example different from Mojang’s implementation:

Take a period shorter than the token expiration limit as the dividing point between valid and temporarily invalid. If the time elapsed since issuance is within this limit, the token is valid; if it exceeds this limit but is still within the expiration limit, the token is temporarily invalid.

This approach achieves the following function: If a player logs in frequently, they do not need to enter a password except for the first login. But when they have not logged in for a long time, they 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 with Profile Name)",
	"password": "Password",
	"clientToken": "clientToken of the token specified by the client (Optional)",
	"requestUser": true/false, // Whether to include user info in response, default false
	"agent": {
		"name": "Minecraft",
		"version": 1
	}
}

If the request does not include clientToken, the server should randomly generate an unhyphenated UUID as clientToken. However, note that clientToken can be any string; meaning any clientToken provided in the request is acceptable and does not have to be an unhyphenated UUID.

Regarding the profile to be bound to the token: If the user has no profiles, it is null; if the user has only one profile, it is typically bound to that profile; if the user has multiple profiles, it is typically null to allow the client to select. That is, if the bound profile is null, the client is required to perform profile selection.

Response Format:

{
	"accessToken": "accessToken of the token",
	"clientToken": "clientToken of the token",
	"availableProfiles": [ // List of available user profiles
		// ,... Each item is a profile (Format see §Serialization of Profile Information)
	],
	"selectedProfile": {
		// ... Bound profile, if null, need not be included (Format see §Serialization of Profile Information)
	},
	"user": {
		// ... User information (Included only when requestUser in request is true, format see §Serialization of User Information)
	}
}

Security Tip: This API can be used for password brute-forcing and should be rate-limited. Limits should target the user, not the client IP.

Login with Profile Name

In addition to logging in with an email, the verification server can also allow users to log in using a profile name. To achieve this, the verification server needs to perform the following work:

When a user logs in using a profile name, the verification server should automatically bind the token to the corresponding profile, meaning selectedProfile in the above response should be the profile used by the user to log in.

In this case, if the user possesses multiple profiles, they can skip the operation of selecting a profile. Considering some programs do not support multiple profiles (e.g., Geyser), profile selection can also be bypassed through this method.

Refresh

POST /authserver/refresh

Revoke the original token and issue a new token.

Request Format:

{
	"accessToken": "accessToken of the token",
	"clientToken": "clientToken of the token (Optional)",
	"requestUser": true/false, // Whether to include user info in response, default false
	"selectedProfile": {
		// ... The profile to select (Optional, format see §Serialization of Profile Information)
	}
}

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

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

If the request includes selectedProfile, then this is a profile selection operation. This operation requires the profile bound to the original token to be null, and the new token will be bound to the profile specified by selectedProfile. If selectedProfile is not included, the profile bound to the new token is the same as the original token.

The refresh operation can still be performed when the token is temporarily invalid. If the request fails, the original token remains valid.

Response Format:

{
	"accessToken": "accessToken of the new token",
	"clientToken": "clientToken of the new token",
	"selectedProfile": {
		// ... Profile bound to the new token, if null, need not be included (Format see §Serialization of Profile Information)
	},
	"user": {
		// ... User information (Included only when requestUser in request is true, format see §Serialization of User Information)
	}
}

Validate Token

POST /authserver/validate

Verify if the token is valid.

Request Format:

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

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

If the token is valid, the server should return HTTP status 204 No Content; otherwise, process it as an invalid token exception.

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; meaning regardless of the value of clientToken, it will not affect the outcome.

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

Signout

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 Tip: This API can also be used to determine the correctness of a password, so it should be subject to the same rate limits as the Login API.

Session Section

Minecraft Server Authentication Flow

The above diagram was created from mc-server-authentication-flow.drawio.

This section is for verification when a profile joins a server. The main process is as follows:

  1. The Minecraft Server and Minecraft Client jointly generate a string (serverId), which can be considered arbitrary.
  2. The Minecraft Client sends the serverId and 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 Joins Server

POST /sessionserver/session/minecraft/join

Record the serverId sent by the server to the client, ready for the server to check.

Request Format:

{
	"accessToken": "accessToken of the token",
	"selectedProfile": "UUID of the profile bound to this token (Unhyphenated)",
	"serverId": "serverId sent by the server to the client"
}

The operation is successful only when accessToken is valid and selectedProfile is consistent with the profile bound to the token.

The server should record the following information:

  • serverId
  • accessToken
  • IP of the client sending this request

Note for implementation: The above information should be recorded in an in-memory database (such as Redis) and should have an expiration time set (e.g., 30 seconds). Given the randomness of serverId, it can serve as the primary key.

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

Server Verifies Client

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

Check the validity of the client session, i.e., whether a record for this serverId exists in the database and the information is correct.

Request Parameters:

ParameterValue
usernameName of the profile
serverIdserverId sent by the server to the client
ip (Optional)Client IP obtained by the Minecraft Server, included only when the prevent-proxy-connections option is enabled

username needs to be the same as the name of the profile bound to the token corresponding to serverId.

Response Format:

{
	// ... Complete information of the profile bound to the token (Includes profile attributes and digital signature, format see §Serialization of Profile Information)
}

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

Profile Section

This section is for querying profile information.

Query Profile Attributes

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

Query complete information for the specified profile (including profile attributes).

Request Parameters:

ParameterValue
uuidProfile UUID (Unhyphenated)
unsigned (Optional)true or false. Whether to exclude the digital signature in the response, default is true

Response Format:

{
	// ... Profile information (Includes profile attributes. If unsigned is false, digital signature must also be included. Format see §Serialization of Profile Information)
}

If the profile does not exist, the server should return HTTP status 204 No Content.

Batch Query Profiles by Name

POST /api/profiles/minecraft

Batch query profiles corresponding to profile names.

Request Format:

[
	"Profile Name"
	// ,... Can have more
]

The server queries profile information corresponding to each profile name and includes it in the response. Non-existent profiles need not be included. There is no requirement for the order of profile information in the response.

Response Format:

[
	{
		// Profile information (Note: Does not include profile attributes. Format see §Serialization of Profile Information)
	}
	// ,... (Can have more)
]

Security Tip: To prevent CC attacks, a maximum value for the number of profiles in a single query needs to be set, which must be at least 2.

Texture Upload

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

Set or clear the texture for the specified profile.

Not all profiles can upload skins and capes. To get the texture types currently uploadable by the profile, see §uploadableTextures Uploadable Texture Types.

Request Parameters:

ParameterValue
uuidProfile UUID (Unhyphenated)
textureTypeTexture type, can be skin (Skin) or cape (Cape)

The request needs to include the HTTP header Authorization: Bearer {accessToken} for authentication. If the Authorization header is missing or the accessToken is invalid, return 401 Unauthorized.

If the operation is successful, return 204 No Content.

The usage of the two HTTP methods PUT and DELETE is described below:

PUT Upload Texture

The Content-Type of the request is multipart/form-data, and the request payload consists of the following parts:

NameContent
model(Only for skins) Texture model of the skin, can be slim (thin arm skin) or an empty string (normal skin).
fileTexture image, Content-Type must be image/png. It is suggested that the client set the filename parameter in Content-Disposition to the filename of the texture image, which can be used by the verification server as a remark for the texture.

If the operation is successful, return 204 No Content.

DELETE Clear Texture

After clearing the texture, the texture of that type will revert to default.

Extended API

The following APIs are designed to facilitate automatic configuration for authlib-injector.

API Metadata Retrieval

GET /

Response Format:

{
	"meta": {
		// Server metadata, content arbitrary
	},
	"skinDomains": [ // Texture domain whitelist
		"Domain matching rule 1"
		// ,...
	],
	"signaturePublickey": "Public key for verifying digital signatures"
}

signaturePublickey is a PEM format public key used to verify the digital signature of profile attributes. It starts with -----BEGIN PUBLIC KEY----- and ends with -----END PUBLIC KEY-----, allowing line breaks in between, but not other whitespace characters (line breaks at the end of the text are also allowed).

Texture Domain Whitelist

Minecraft will only download textures from domains in the whitelist. If the domain of the texture URL is not in the whitelist, a Textures payload has been tampered with (non-whitelisted domain) error will occur. The reason for adopting this mechanism is see MC-78491.

The texture whitelist defaults to include two rules: .minecraft.net and .mojang.com. You can set the skinDomains attribute to add extra whitelist rules. The rule format is as follows:

  • If the rule starts with . (dot), it matches domains ending with this rule.
    • For example, .example.com matches a.example.com, b.a.example.com, but does not match example.com.
  • If the rule does not start with . (dot), the matched domain must be exactly the same as the rule.
    • For example, example.com matches example.com, but does not match a.example.com, eexample.com.

Metadata in meta

There is no mandatory requirement for the content in meta; the following fields are all optional.

Server Basic Information
KeyValue
serverNameServer Name
implementationNameServer Implementation Name
implementationVersionServer Implementation Version
Server URLs

If you need to display the verification server homepage address, registration page address, etc., in the launcher, you can add a links field in meta.

The type of the links field is an object, which can contain:

KeyValue
homepageVerification Server Homepage Address
registerRegistration Page Address
Feature Options

The fields marked with (advanced) below are advanced options and typically do not need to be set.

KeyValue
feature.non_email_loginBoolean, indicates whether the verification server supports logging in with credentials other than email (such as profile name), default is false.
For details, see §Login with Profile Name.
feature.legacy_skin_api(advanced) Boolean, indicates whether the verification server supports the legacy skin API, i.e., GET /skins/MinecraftSkins/{username}.png.
When not specified or false, authlib-injector will use the built-in HTTP server to handle requests to this API locally; if true, requests will be handled by the verification server.
For details, see the -Dauthlibinjector.legacySkinPolyfill option in README § Parameters.
feature.no_mojang_namespace(advanced) Boolean, whether to disable authlib-injector’s Mojang namespace (@mojang suffix) feature, default is false.
For details, see the -Dauthlibinjector.mojangNamespace option in README § Parameters.
feature.enable_mojang_anti_features(advanced) Boolean, whether to enable Minecraft’s anti-features, default is false.
For details, see the -Dauthlibinjector.mojangAntiFeatures option in README § Parameters.
feature.enable_profile_key(advanced) Boolean, indicates whether the verification server supports Minecraft’s message signature key pair feature (i.e., digital signatures for chat messages in multiplayer games), default is false.
When enabled, Minecraft will obtain the key pair issued by the verification server via the API POST /minecraftservices/player/certificates. When not specified or false, Minecraft will not include digital signatures in chat messages.
For details, see the -Dauthlibinjector.profileKey option in README § Parameters.
feature.username_check(advanced) Boolean, indicates whether authlib-injector enables username validation functionality, default is false.
When enabled, players with special characters in their username will be unable to join the server. For details, see the -Dauthlibinjector.usernameCheck option in README § Parameters.

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 Location Indication (ALI)

API Location Indication (ALI) is an HTTP response header field X-Authlib-Injector-API-Location, which serves the purpose of service discovery. The value of ALI is a relative URL or an absolute URL, pointing to the Yggdrasil API associated with the current page.

By using ALI, users only need to enter an address associated with the Yggdrasil API, without inputting the actual API address. For example, https://skin.example.com/api/yggdrasil/ can be simplified to skin.example.com. Launchers supporting ALI will request (https://)skin.example.com, recognize the ALI header field in the response, and find the real API address based on it.

Skin sites can enable ALI on the homepage or site-wide. The method to enable ALI is to add the X-Authlib-Injector-API-Location header field to the HTTP response, for example:

X-Authlib-Injector-API-Location: /api/yggdrasil/  # Use relative URL
X-Authlib-Injector-API-Location: https://skin.example.com/api/yggdrasil/  # Absolute URL is also allowed, supports cross-origin

When a page’s ALI points to itself, this ALI will be ignored.

See Also

Reference Implementation

yggdrasil-mock is the reference implementation for this specification.