User:Rayanth/SSO
SSO is used by third-party apps to authenticate a specific character with CCP's Servers, so that additional character-specific information (and corporation-specific information, if the character has appropriate rights) can be requested from ESI.
For third party developers, here is a brief overview of the process to get an authenticated token with SSO:
- Request authentication - this is accomplished by redirecting the user to CCP's login page, with some identifying information for your app.
- SSO/CCP handle all of the user/password stuff and redirect the user to your redirect page, including an Authentication Token valid for 5 minutes
- Your app returns the Authentication Token to SSO behind the scenes, along with your client ID and secret key.
- SSO responds, behind the scenes, with an Access Token (good for 20 minutes) and a Refresh Token (no expiration unless revoked)
- You include the Access Token with any future requests to ESI that require authentication.
Note that an Authentication Token and Access Token are not the same thing. The Authentication Token will not give you the ability to see any character data - it's just an extra step to prevent "man in the middle" attacks as well as keep your secret key hidden from the end user.
The Access Token is good only for the scopes you originally requested. You can have multiple Access Tokens, each with their own combinations of Scopes, but bear in mind that each one will show up independently on the user's Third Party Applications page and it may be confusing for them.
Detailed example using curl
Here is a more detailed step-by-step sequence to authenticate a character with SSO v2. In this example, we will be requesting access to the character's Blueprints. All information has been fictionalized, and the important parts have been color-coded for clarity.
Step 1: Request Authentication. Use the following information to construct a URL to direct the user to:
response_type | code | (literal word code) - indicate we're requesting a code. |
redirect_uri | https%3A%2F%2Feve.example.com%2Fredirect | URL-encoded version of your application's registered redirect callback URL. |
client_id | 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d | your application's registered client id. |
scope | esi-characters.read_blueprints.v1 | the scope (or scopes) you are requesting. |
state | foo_bar | can be anything you want, used to verify the response from SSO, and can be used to match a response to a user session in heavier environments if unique. |
Construct these values into a GET request and direct the user to it (e.g. via a "Log In to EVE Online" button)
Step 2: The user will be directed to the familiar SSO Login screen, all of which is handled by CCP. You will never have to worry about handling the User's EVE Account password, and you won't even know what their account name is. Once the user has logged in via SSO, they will be redirected to your redirect URL with additional information for you to process:
The value following 'code' (here: uHkc5DPnI0CKOxJ_ixVMpg) is your Authentication Token and is valid for 5 minutes.
You should also verify that the value following 'state' (here: foo_bar) is the value that you sent in Step 1 above.
Step 3:Authenticate and request an Access Token
The remainder of the steps will occur behind the scenes from the user's perspective. Here, we will use curl
so that you can follow along manually without worrying about understand a particular programming language for an example.
curl
is a command-line utility that can be used in Linux shell, or Windows PowerTools (although the PowerTools version might have slightly different syntax. This example was written in Linux.)- Notably:
-H
is used to set Headers for POST requests, and-d
is used to set form values.
This step requires the following values to be sent as a POST request:
Header | Authorization: | Basic [client_id:secret] | The value will be your base64-encoded client_id and secret key, separated by a colon. |
Header | Content-Type: | application/x-www-form-urlencoded | Indicate to the server that the POST values are URL-encoded as Form values. |
Form | grant_type | authorization_code | Indicate to the server that we are giving it an Authorization Code (Received in Step 2). |
Form | code | uHkc5DPnI0CKOxJ_ixVMpg | The code received in Step 2. |
For [client_id:secret], combine the two into a single string conjoined with a colon, and then base64 encode that string.
In this example we are using:
- client_id = 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
- secret_key = ZtHf5awlFvkVEJX39kG6mGU1jZAzlClhTp4DgsUM
- client_id = 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
We combine them into one string, separated by a colon
1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d:ZtHf5awlFvkVEJX39kG6mGU1jZAzlClhTp4DgsUM
And then base64 encode it (for testing, you can just use https://www.base64encode.org/)
- MWEyYjNjNGQ1ZTZmN2E4YjljMGQxZTJmM2E0YjVjNmQ6WnRIZjVhd2xGdmtWRUpYMzlrRzZtR1UxalpBemxDbGhUcDREZ3NVTQ==
This is the value you will use for 'Authorization'.
For a live site, generate the value using your own programming code and not a website such as above.
You may wish to generate it on-demand without storing it on the server, in case you should have to change your secret key in the future.We can now construct the curl request:
Recall that -H is setting Headers and -d is setting values.
Step 4: Receive Access Token
If everything went well, the response from the server is a json string containing the following values:
access_token | a JSON Web Token containing the credentials. |
expires_in | the remaining time until the access token expires, in seconds - usually 1199 or 1200 (20 minutes) |
token_type | Should be "Bearer" - used to distinguish from Basic in the Auth phase (Step 3). Use Bearer tokens to query ESI. |
refresh_token | Used to generate a new Access Token when the old one expires. |
The Access Token can be unpacked and used to verify the request for additional security against MITM attacks, but that is beyond the stcope of this article. Refer to ESI Docs for more information. In this example, we will just use the token as-is.
If Access Tokens get out in the wild, they're only good for 20 minutes.
Refresh Tokens have no expiration. If you find your store of Refresh Tokens compromised, you must revoke them all.(For this example, I am using a randomly generated string as a stand-in for the Access Token. It is not a valid JWT token, but it is of the appropriate length)
The response from the server should look like this:
Step 5: Validating the character
The JWT Token gives us very little information about the character. Further, some of that information could change if the character is sold on the Character Bizarre, or otherwise changes hands, so it should not be considered reliable in the long-term. To get more information about the character, you can use the 'verify' SSO endpoint. This is the only endpoint in the SSO sequence where "v2" is not used.
Note that the only thing we are sending to this endpoint is the Access Token, sent as a Header. We will get a return such as:
Here's a breakdown of that information:
CharacterID | The CharacterID this Access Token was issued for. |
CharacterName | The Character Name associated with that CharacterID. |
ExpiresOn | The expiration date/time (UTC) of the Access Token. |
Scopes | The requested scopes that have been granted to this Access Token. |
TokenType | What information the token has access to. Currently always Character. |
CharacterOwnerHash | A unique hash composed of both the Character info and the Owner (account) info. |
IntellectualProperty | Defines the information as owned by EVE/CCP |
The CharacterOwnerHash is unique for the unique combination of that character AND the current owner (account) of that character. It cannot be unpacked, so it is secure enough to store in your database and use for validating future responses. If the Character changes hands, then this hash will change - and you will know that your application should disassociate it with the prior owner's login to your application/website, since the prior owner should no longer be allowed to see what the Character is doing.
Of significant importance is the CharacterID -- all of your ESI calls against a character will include this ID, not the name.
Step 6: There is no Step 6.
Seriously. No step 6.
Step 7: Requesting the Blueprints
We're now ready to request character-specific information! We have our access token fully validated, with the Blueprints Scope, and we know the Character ID of the user we're working with. All that's left is to send the request.
Authenticated requests are sent with the authentication information in the Headers, and the actual request in the URL itself. For this example, we want a character's blueprints, which is the URL
Here is what the curl command looks like:
In this example above, I have only set the "Accept Encoding" to "txt" because I want to be able to read the result. For larger datasets in your application, you should consider allowing and using gzip compression.
The response will be a JSON package of JSON strings - one string for each blueprint:
or, made prettier:
{"item_id":306253293,"location_flag":"Hangar","location_id":60003760,"material_efficiency":9,"quantity":-2,"runs":1,"time_efficiency":14,"type_id":21858},
{"item_id":901633770,"location_flag":"Hangar","location_id":60003760,"material_efficiency":9,"quantity":-2,"runs":1,"time_efficiency":20,"type_id":16234},
{"item_id":1072789749,"location_flag":"Hangar","location_id":60003760,"material_efficiency":10,"quantity":-1,"runs":-1,"time_efficiency":20,"type_id":1143}
Here's the information breakdown:
item_id | A unique ID indicating this particular instance of the item. |
location_flag | What type of location the item is currently in (Hangar, Station, Cargo, etc) |
location_id | The identified for that location (here, 60003760 is Jita 4-4) |
material_efficiency | The current ME level of the blueprint |
quantity | How many are in the stack. -1 indicates "singleton", or non-stackable (item is not packaged. BPO's are unpackaged when they are researched). -2 indicates BPC. |
runs | number of runs remaining. -1 indicates BPO, as they have no run limits. |
time_efficiency | The current TE level of the blueprint |
type_id | Parent Type ID for the blueprint. Note this is NOT the TypeID of the item it makes. |
Refreshing an Access Token
After 20 minutes, an Access Token expires. If an expired Access Token is sent to SSO, it will reply with:
{"error":"token is expired","sso_status":200}
To get a new Access Token, send the Refresh Token from Step 4 back in the same manner as Step 3, but changing the "grant_type" to "refresh_token":
The response will be a new Access Token, once again good for 20 minutes, as well as the same refresh token that was already issued:
Helpful Links
The following links will be helpful in your SSO and ESI application development:
EVE Swagger Interface explorer - contains information on all of the endpoints and what they return.
ESI Docs - open-source documentation for ESI.
Fuzzwork - run by Steven Ronuken, who is a long-time CSM member. Hosts the converted SDE databases as well as other good info.
Tweetfleet Slack - a Slack workspace where many ESI developers hang out, as well as several CCP developers. A great place to ask questions. The link to get an invite is on Fuzzwork: https://www.fuzzwork.co.uk/tweetfleet-slack-invites/
EVE Resources - hosts the Icons/Renders/Types image repositories, and the original Static Data Export (SDE). Also the Developer's License Agreement, which is important to undstand.
[1] - Third Party Developer Blog, from CCP - has many useful articles for how to use ESI/SSO, and also has the link to create/manage your own third party application details.