-
Notifications
You must be signed in to change notification settings - Fork 1
GW2Auth Developer Guide
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:
- A user clicks a login button inside your application
- Your application prepares the authorization
- Your application prepares a authorization URL containing the permissions you request
- Your application redirects the user to the prepared authorization URL
- GW2Auth handles the authorization request
- GW2Auth detects that the user has not yet permitted your application access
- GW2Auth shows a consent page where the user can see which permissions you requested
- The user selects the GW2 Accounts they are willing to share the requested permissions with you
- The user clicks "Authorize" on the consent page
- GW2Auth saves the users consent
- GW2Auth redirects the user back to your application
- Your application handles the redirect
- The redirect contains a
code
query parameter - Your application uses the
code
parameter together with its ClientID and ClientSecret to request an OAuth2 AccessToken and RefreshToken - The OAuth2 AccessToken is a JWT. It contains a field
sub
which uniquely identifies the user (UserID) - Using the UserID, your application persists the AccessToken and RefreshToken
- The following steps may be performed without the user being present (sitting at their device)
- The redirect contains a
- Your application uses the provided information
- 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.
- Using these subtokens, your application can perform GW2 API Requests
- The OAuth2 AccessToken and each of the subtoken are valid for 30 minutes upon creation.
- Refresh the OAuth2 AccessToken
- 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.
- Repeat from Step 4.3.
If a user visits your application again and the user is not logged in to your application anymore:
- A user clicks a login button inside your application
- 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
- 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
- The same flow as in the First login case takes place
You can learn more about OAuth2.0 here.
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.
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.
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 |
When your application requests a user to login, your application should perform the following steps.
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.
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.
Store this using the previously generated state
as the key.
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.
This parameter specifies the permissions your application wants to request.
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 ... |
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) |
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
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.
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 |
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.
AccessTokens returned by GW2Auth are JWTs. The payload of these JWTs is a JSON object containing information based on the requested scopes.
{
"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
}
{
"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
}