Skip to content

GW2Auth Developer Guide

Felix edited this page Jul 25, 2024 · 7 revisions

General

GW2Auth is a OAuth2 Authorization Server. Through GW2Auth, you can provide your users with a simple way to login to your application and share their GW2 API Tokens with you through the OAuth2 protocol.

The top-level flow looks like this:

First login

  1. A user clicks a login button inside your application
  2. Your application prepares the authorization
    1. Your application prepares a authorization URL containing the permissions you request
    2. Your application redirects the user to the prepared authorization URL
  3. GW2Auth handles the authorization request
    1. GW2Auth detects that the user has not yet permitted your application access
    2. GW2Auth shows a consent page where the user can see which permissions you requested
    3. The user selects the GW2 Accounts they are willing to share the requested permissions with you
    4. The user clicks "Authorize" on the consent page
    5. GW2Auth saves the users consent
    6. GW2Auth redirects the user back to your application
  4. Your application handles the redirect
    1. The redirect contains a code query parameter
    2. Your application uses the code parameter together with its ClientID and ClientSecret to request an OAuth2 AccessToken and RefreshToken
    3. The OAuth2 AccessToken is a JWT. It contains a field sub which uniquely identifies the user (UserID)
    4. Using the UserID, your application persists the AccessToken and RefreshToken
    5. The following steps may be performed without the user being present (sitting at their device)
  5. Your application uses the provided information
    1. Depending on the requested permissions, the OAuth2 AccessToken may contain various kinds of information. If you requested any GW2 API Permissions, the AccessToken also contains a GW2 API Subtoken for each of the GW2 Accounts the user decided to share with your application on the consent page.
    2. Using these subtokens, your application can perform GW2 API Requests
    3. The OAuth2 AccessToken and each of the subtoken are valid for 30 minutes upon creation.
  6. Refresh the OAuth2 AccessToken
    1. Once the OAuth2 AccessToken becomes invalid (or the remaining valid time is not enough for the actions you intend to make), your application requests a new pair of OAuth2 AccessToken and RefreshToken at GW2Auth, using your applications ClientID and ClientSecret together with the RefreshToken.
    2. Repeat from Step 4.3.

Subsequent logins

If a user visits your application again and the user is not logged in to your application anymore:

  1. A user clicks a login button inside your application
  2. Your application prepares the authorization 2.1. Your application prepares a authorization URL containing the permissions you request 2.2. Your application redirects the user to the prepared authorization URL
  3. GW2Auth handles the authorization request 3.1. GW2Auth detects that the user has already permitted your application access 3.2. GW2Auth redirects the user back to your application
  4. The same flow as in the First login case takes place

You can learn more about OAuth2.0 here.

Create a client

To start using GW2Auth in your application, you first have to create a OAuth2 Client at GW2Auth.

The creation of clients at GW2Auth is completely self-service. You can create and delete clients any time. Simply go to Clients and click the button Register a new Client.

Choose a name for your client (this name will be displayed to the users) and fill in the Redirect URI. This URI may be changed and/or more URIs may be added at any time later.

For local testing purposes, use http://127.0.0.1. localhost is not allowed. If you want to see how an authorization would look like, you can use https://gw2auth.com/account/client/debug.

Once you have filled in all required values, click Create Client. You will now see an overview of your newly created client.

The following shown values are important:

  • ClientID: This is the ID uniquely identifying your application. It is not considered private information.
  • ClientSecret: This is the ClientSecret your application must use to request OAuth2 AccessToken/RefreshToken pairs at GW2Auth. This should be kept private and can not be shown to you by GW2Auth later. You request a new ClientSecret at any time, but this will invalidate the old one at the same time.

GW2Auth integration

As mentioned earlier, GW2Auth uses OAuth2.0. For most web frameworks, you'll find an existing implementation which takes care of all the details. The following guide will show you the basics of how it works, but it's strongly advised to not implement this on your own whenever possible.

OAuth2 Endpoints

All relevant OAuth2.0 URLs can be found at https://gw2auth.com/.well-known/oauth-authorization-server If possible, your application should fetch this URL to retrieve the required OAuth2 configuration. Some frameworks may only ask for an "IssuerURI", which would be https://gw2auth.com/ in this case.

The most important properties returned by this URL are:

Key Description
authorization_endpoint The base URL to initiate an authorization request
token_endpoint The URL used by your application to request OAuth2 AccessToken/RefreshToken pairs
jwks_uri The URL to fetch GW2Auths public keys to verify the integrity of OAuth2 AccessTokens returned by GW2Auth

Login using GW2Auth

When your application requests a user to login, your application should perform the following steps.

Generate the state parameter

For each authorization request, your application should generate a state parameter. Your application may encode any information in this parameter, or simply generate a random string. The user will later be redirected back to your application using the exact same value.

(Optional, but recommended) Generate the code_challenge parameter

GW2Auth supports both PKCE and plain CodeChallenges. This is optional, but recommend.

Generate a random string (recommended length: 128 characters) and store it on your side.

For applications with a backend:

Store this using the previously generated state as the key.

For exclusively client-side applications:

Store this in LocalStorage or similar.

For PKCE CodeChallenge, generate the SHA256 hash of the code_challenge value and encode it using Base64 URL Encoding.

Generate the scope parameter

This parameter specifies the permissions your application wants to request.

Scopes supported for all clients
Scope Description
gw2:account GW2 API "account" permission for all GW2-Accounts selected by the user
gw2:builds GW2 API "builds" permission ...
gw2:characters GW2 API "characters" permission ...
gw2:guilds GW2 API "guilds" permission ...
gw2:inventories GW2 API "inventories" permission ...
gw2:progression GW2 API "progression" permission ...
gw2:pvp GW2 API "pvp" permission ...
gw2:wvw GW2 API "wvw" permission ...
gw2:tradingpost GW2 API "tradingpost" permission ...
gw2:unlocks GW2 API "unlocks" permission ...
gw2:wallet GW2 API "wallet" permission ...
Scopes for clients using API Version 0
Scope Description
gw2auth:verified Adds an additional field "verified" for all GW2 Accounts selected by the user. This represents the users GW2 Account Verification status at GW2Auth (more information below)
Scopes for clients using API Version 1
Scope Description
id Adds the users unique UserID to the AccessToken
gw2acc:name Add the displayname (i.e. Felix.9127) for all GW2 Accounts selected by the user
gw2acc:display_name Add the GW2Auth displayname (i.e. My Main Account) for all GW2 Accounts selected by the user
gw2acc:verified Add the field "verified" for all GW2 Accounts selected by the user. This represents the users GW2 Account Verification status at GW2Auth (more information below)

The final scope parameter must be a string containing all scopes you wish to request, separated by a single whitespace.

Examples:

  • V0 client: gw2auth:verified gw2:account gw2:characters
  • V1 client: id gw2acc:verified gw2:account gw2:characters

Redirect the user to the authorization_endpoint

Next, your application should redirect the user to the authorization_endpoint using the following parameters passed in the query:

Query Parameter Description Example Value
response_type Must be code code
client_id OAuth2 ClientID of your Client d5dba13c-42d7-4c78-b6ea-9bcb7407112b
state state parameter you generated on your side (see above) ioasho889dasdknadjhiahdiohi1290asin
scope List of scopes you want to request (see above). Must not be empty. In any order. gw2:account gw2:characters
redirect_uri One of the configured redirect URIs. The user will be redirected to this URI in the next step https://gw2auth.com/account/client/debug
name (Optional) A display name for this authorization. If not passed, the internal authorization ID will be used instead. Only pass this if you can provide a meaningful name - for desktop applications this could be the name of the current machine for example. DESKTOP-12345
prompt (Optional) Pass the value consent if you wish to show the consent (and GW2 Account selection) to the user even if they have an existing authorization with your client consent
code_challenge (Optional) If you decided to use a code_challenge (see above), pass it here ihsodhasiodzdadhasoifzhaioszfiaozd
code_challenge_method (Optional) If you decided to use a code_challenge (see above), this must be passed too. Must be either S256 or plain S256

The user will now be shown a dialog where they have to select all GW2 Accounts they want to share with your application.

Important note: Due to the nature of redirects (they're handled on the client side), it is technically possible for a user to modify the Location-Header of your redirect, keep the state parameter but modify the scope-Parameter. We will come back to this below.

Waiting for the authorization response

Once the user either approved or declined the requested authorization, they will be redirected to the URI given in the initial request.

If the user declined the authorization (by clicking Cancel on the authorization page), the following Query Parameters will be present in the request arriving at your application:

Query Parameter Description Example Value
error OAuth2 Error Code access_denied
error_description Description of the error The user has denied your application access.

If the user approved the authorization, the following Query Parameters will be present in the request arriving at your application:

Query Parameter Description Example Value
code A short-lived one time code which is used by your application to retrieve the initial OAuth2 AccessToken/RefreshToken pair wGS4oW0Dm8hPxlY9EF3lhRBeXvBAoQu2iLgwONa2CaVqftHog0dPJ4zCOnFkFkVWQXZbLYH434iGuJFScNOvQgAlrw8ZsO6hquFxSG7y3HUpSWKrXl3IBF4-UOzatEP-
state The state parameter passed to the redirection in the step before ioasho889dasdknadjhiahdiohi1290asin

Request the initial OAuth2 AccessToken/RefreshToken pair

Inside the handler of your application which processes the redirect to your application, your application can now use the code parameter together with its ClientID and ClientSecret to request the initial AccessToken/RefreshToken pair at GW2Auth.

Before doing so, your application should verify the state parameter which is also passed in the query (see above). If this parameter looks fine (what is "fine" entirely depends on how you generated the state originally), you may continue.

To request the AccessToken and RefreshToken, perform a POST request to the token_endpoint using the following values either in the query or as multipart formdata:

Parameter Description Example Value
grant_type Must be authorization_code authorization_code
code The code parameter passed in the query of the request your application is currently processing wGS4oW0Dm8hPxlY9EF3lhRBeXvBAoQu2iLgwONa2CaVqftHog0dPJ4zCOnFkFkVWQXZbLYH434iGuJFScNOvQgAlrw8ZsO6hquFxSG7y3HUpSWKrXl3IBF4-UOzatEP-
client_id The ClientID of your application d5dba13c-42d7-4c78-b6ea-9bcb7407112b
client_secret The ClientSecret of your application Sx9skfRMXxIVfX7UWaktKP9od1pV7kLGDSBzdsvZIZ1xdApzrOHTfom6m8XNrpVa
redirect_uri The exact same redirect_uri that has been passed to the initial authorization request https://gw2auth.com/account/client/debug
code_verifier (Optional) The plain value of the code_challenge generated earlier. Required if a code_challenge was used in the authorization request. ihsodhasiodzdadhasoifzhaioszfiaozd

A successful response will look like this:

{
   "access_token": "" // OAuth2 AccessToken (JWT),
   "refresh_token": "uVKRO-gElNXaAF_VNg-6u8jbmtlazLDNC3ljPiX92rCY3j59Oe6Lt3V2PnZJZEny6ZMUQ8FCZkjzLMfXrNgymvBLW4RYEpPSLb7QRX5QtPaGIhqsXKcufJrX3jR1bJBw",// OAuth2 RefreshToken used to request a new AccessToken/RefreshToken pair later (without user interaction)
   "scope":"gw2:account gw2:characters",// the authorized scopes (see important note below)
   "token_type": "Bearer",
   "expires_in": 1799 // seconds until the AccessToken expires
}

Important note: As mentioned above, a user can technically manipulate the scopes passed in the initial redirect. Make sure to verify the scope value here!

Persist the refresh_token as you wish. This token can be used to request a new AccessToken/RefreshToken pair. AccessTokens are valid for 20-30 minutes, while RefreshTokens are valid for 180 days. To request a new pair, your application must always use the latest RefreshToken.

Read the information provided in the AccessToken JWT

AccessTokens returned by GW2Auth are JWTs. The payload of these JWTs is a JSON object containing information based on the requested scopes.

Example JWT payload for clients using API Version 0

{
	"sub": "3f1e6487-1150-4e6d-af10-fb9f3bc275a4",// unique ID for the user
	"aud": "3c74b3c1-1792-460b-bf8b-457fffd7d7cd",// the ClientID of your application
	"gw2:tokens": {
		// these keys are GW2 Account IDs ("id" field of https://api.guildwars2.com/v2/account)
		"89172d0f-138f-eb11-81b9-02de0bb68f60": {
			"name": "FelixAlt.3172 (Alt 1)",// GW2Auth display name
			"verified": true,// verification status at GW2Auth (only present with gw2auth:verified scope)
			"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwcXZvWktzak11N09oNnhrdDFHVXBtbzU0d0ctajhTanB1aHhHVy1aMGt3IiwiaWF0IjoxNjkzNTM3ODk1LCJleHAiOjE2OTM1Mzk2OTUsInBlcm1pc3Npb25zIjpbImNoYXJhY3RlcnMiLCJwdnAiLCJhY2NvdW50IiwidW5sb2NrcyJdfQ.QqzAJ_kZ4mb2t1aaCeup3268FWsrUPjyF9hMsaV3AXo"// GW2 API subtoken; your application can use this token to perform requests to the GW2 API
		},
		"93df54c6-78f7-e111-809d-78e7d1936ef0": {
			"name": "Felix.9127 (Main)",
			"verified": false,// the verification status may be true or false
			"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJFbmlHMGZPdzlPbjBLbzVBa0xSd1lwanFWYXN2Wjg1V2lGQTJSQWs0Z2k0IiwiaWF0IjoxNjkzNTM3ODk0LCJleHAiOjE2OTM1Mzk2OTUsInBlcm1pc3Npb25zIjpbImNoYXJhY3RlcnMiLCJwdnAiLCJhY2NvdW50IiwidW5sb2NrcyJdfQ.-Q3Kx6S_8VPy2F8D6n8EDvMWN_Ts1kvIow69RitFqhE"
		},
		"fb6abd6e-1d8f-eb11-81b9-02de0bb68f60": {
			"name": "FelixAlt.6517 (Alt 2)",
			"verified": true,
			"error": "Failed to obtain new subtoken"// Only present if, for some reason, GW2Auth was unable to retrieve a new subtoken for this GW2 Account. Always either "error" OR "token" is present. Your application may directly refresh the AccessToken/RefreshToken pair if an "error" is present
		}
	},
	// list of gw2 api permissions
	"gw2:permissions": [
		"account",
		"unlocks",
		"characters",
		"pvp"
	],
	// list of authorized scopes
	"scope": [
		"gw2:characters",
		"gw2auth:verified",
		"gw2:account",
		"gw2:unlocks",
		"gw2:pvp"
	],
	"iss": "https://gw2auth.com",// the issuer of this JWT
	"nbf": 1693537895,// "not before" unix timestamp (seconds): do not use this token prior to this timestamp
	"exp": 1693539695,// "expiration" unix timestamp (seconds): do not use this token later than this timestamp
	"iat": 1693537895// "issued at" unix timestamp (seconds): timestamp when this JWT was created
}

Example JWT payload for clients using API Version 1

{
	"sub": "3f1e6487-1150-4e6d-af10-fb9f3bc275a4",// unique ID for the user (without "id" scope, this value changes on every refresh)
	"aud": "3c74b3c1-1792-460b-bf8b-457fffd7d7cd",// the ClientID of your application
	"gw2_accounts": [
		{
			"id": "89172d0f-138f-eb11-81b9-02de0bb68f60",// GW2 Account ID ("id" field of https://api.guildwars2.com/v2/account)
			"name": "FelixAlt.3172",// GW2 display name (only present with gw2acc:name scope)
			"display_name": "FelixAlt.3172 (Alt 1)",// GW2Auth display name (only present with gw2acc:display_name scope)
			"verified": true,// verification status at GW2Auth (only present with gw2acc:verified scope)
			"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIwcXZvWktzak11N09oNnhrdDFHVXBtbzU0d0ctajhTanB1aHhHVy1aMGt3IiwiaWF0IjoxNjkzNTM3ODQ1LCJleHAiOjE2OTM1Mzk2NDUsInBlcm1pc3Npb25zIjpbInRyYWRpbmdwb3N0IiwiY2hhcmFjdGVycyIsIndhbGxldCIsImFjY291bnQiLCJpbnZlbnRvcmllcyJdfQ.mArk_vlhTG330EISzkydJlkwHir99sUSXnQ7T0G2EeA"// GW2 API subtoken; your application can use this token to perform requests to the GW2 API (only present with at least one GW2 API permission scope, i.e. gw2:account)
		},
		{
			"id": "93df54c6-78f7-e111-809d-78e7d1936ef0",
			"name": "Felix.9127",
			"display_name": "Felix.9127 (Main)",
			"verified": false,// the verification status may be true or false
			"error": "Failed to obtain new subtoken"// Only present if, for some reason, GW2Auth was unable to retrieve a new subtoken for this GW2 Account. Always either "error" OR "token" is present. Your application may directly refresh the AccessToken/RefreshToken pair if an "error" is present
		},
		{
			"id": "fb6abd6e-1d8f-eb11-81b9-02de0bb68f60",
			"name": "FelixAlt.6517",
			"display_name": "FelixAlt.6517 (Alt 2)",
			"verified": true,
			"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJRRmFoWkt5el8tbjlZTEVZUWpKaXVjR1dWaFNQUWN3M3NGSF9ncGF4em5jIiwiaWF0IjoxNjkzNTM3ODQ1LCJleHAiOjE2OTM1Mzk2NDUsInBlcm1pc3Npb25zIjpbInRyYWRpbmdwb3N0IiwiY2hhcmFjdGVycyIsIndhbGxldCIsImFjY291bnQiLCJpbnZlbnRvcmllcyJdfQ.dYzC40a_tyS0YdEDTk0gZTb8LAYPXvpd7A9qYpV4Tu4"
		}
	],
	// list of authorized scopes
	"scope": [
		"gw2:characters",
		"gw2acc:display_name",
		"gw2acc:name",
		"gw2acc:verified",
		"gw2:inventories",
		"gw2:account",
		"gw2:tradingpost",
		"id",
		"gw2:wallet"
	],
	"iss": "https://gw2auth.com",// the issuer of this JWT
	"nbf": 1693537895,// "not before" unix timestamp (seconds): do not use this token prior to this timestamp
	"exp": 1693539695,// "expiration" unix timestamp (seconds): do not use this token later than this timestamp
	"iat": 1693537895// "issued at" unix timestamp (seconds): timestamp when this JWT was created
}