From EVE University Wiki
Jump to: navigation, search

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:

  1. Request authentication - this is accomplished by redirecting the user to CCP's login page, with some identifying information for your app.
  2. 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
  3. Your app returns the Authentication Token to SSO behind the scenes, along with your client ID and secret key.
  4. SSO responds, behind the scenes, with an Access Token (good for 20 minutes) and a Refresh Token (no expiration unless revoked)
  5. 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 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

We combine them into one string, separated by a colon


And then base64 encode it (for testing, you can just use


This is the value you will use for 'Authorization'.

Do not share your secret_key or this base64 encoded version publicly.

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:

curl -XPOST -H 'Authorization: Basic MWEyYjNjNGQ1ZTZmN2E4YjljMGQxZTJmM2E0YjVjNmQ6WnRIZjVhd2xGdmtWRUpYMzlrRzZtR1UxalpBemxDbGhUcDREZ3NVTQ==' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Host:' -d 'grant_type=authorization_code&code=uHkc5DPnI0CKOxJ_ixVMpg' ''

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.

Store Refresh Tokens as securely as you can.

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.

curl -XGET -H 'Authorization: Bearer yJhbGciOiJSUzI1NiIsImtpZCI6IkpXVC1TaWduYXR1cmUtS2V5IiwidHlwIjoiSldUIn0.eyJzY3AiOiJlc2ktY2hhcmFjdGVycy5yZWFkX2JsdWVwcmludHMudjEiLCJqdGkiOiJiY2M3YmMwOC1kOGViLTRiODctYjk2NS0zMzU5NTI2ZTkyMDkiLCJraWQiOiJKV1QtU2lnbmF0dXJlLUtleSIsInN1YiI6IkNIQVJBQ1RFUjpFVkU6MTA2NjExNjUxOSIsImF6cCI6IjRmZWU5Yzc4MTM0ODRjZmU4ODE0MmVjNThmMDI1NjNlIiwibmFtZSI6IlBhbnRhbCIsIm93bmVyIjoibmpUTTBWK2Z2a1dSRGlxRkhveEw2Tnlmalg0PSIsImV4cCI6MTU1Mjg3MzA2NCwiaXNzIjoibG9naW4uZXZlb25saW5lLmNvbSJ9.gBvVG2Yi-uOXxIoXmH-U-ou5nJMvuicomsk5voR-ktfy587G_Vu-h8qwrBTY4g30m7Pa2y1LIF1pIhVxdlX5-bkuqfzRHjRIFz1JAghc9yc1sVU1MzRQ3VPchijKULHikHhBA8pyqFlkq_JILn_feG347OAEjt6aLXTOWK9IyiwEFWEzbATcY61VhnxFDlT4HWYNy-oQKVSVJnFU2UAfGP6xx-6VUE9XooAb4Qh20zxlCVaV9M16Gc9RQz5hSzHOKMbuFLwAt5YtftEQWdux_JyIrwMEB8vzfMATFB1IRAX9ZO_ucO4IeqT3GpmY30NP869NInHJmET3d9PdkS-EQw'

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:

{"CharacterID":123123,"CharacterName":"Some Bloke","ExpiresOn":"2019-09-02T01:37:44","Scopes":"esi-characters.read_blueprints.v1","TokenType":"Character","CharacterOwnerHash":"CMRKdV+3TypQTTtZiHlVmerkbBS=","IntellectualProperty":"EVE"}

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{characterid}/blueprints/

Here is what the curl command looks like:

curl -XGET -H 'Authorization: Bearer eyIQPpbD0WSXswejTl4mE2WNgIgQRu04AWBYOPFLMXJtF4WOoQLmb21J6NWYcKlq7HKHOXd.eyVFnN5amjB3Grr2I8gusFPX7E9vyaA3MCchiq507QsPCa0fEtQyTh5OAEQ82YXJPojaKRLLRL1RXr4hE9H0H0nWW3QOEOhO4l2N2C3paY9DvBU08A9fDyAOtspURa5ZyJsjmvBOV5JcJ3z6ifDJctPlj7UAsWXiwxb1qJXQOEdeZTFFTkyUE2aIs38tjUKJTnRURPvoSsY7umMCys2dZInalm3VUBg6nNRv57VdK2lWM0BHiZj8CTC62jLOOgWOyT0phmvn1A5pQ5eb6IIYnm7R6fMOfLUbolNp16sk9buwHVITQOzSEHP0hAjUrHG6w3ERDqTWcZBlE1ZZOWQdsAVw6SO4pSKbQkMOd4j9iTOWbtukQNJj7bophu2zhUVX.3YxmzwcZ-4Cubl01aIF-i-hHipaA863eYoBce8s-pbaYbF2q_6G-5F1GfLYtKTjS2autC9YNHwX8awr4l0aJ-H4AijuShlT2ItJHarEtqjlbOBTLA7BbiwtOmnrckeFi40SZQ6iXEpDKo_23cR_WdTQjWGwXSf0fr4e1xK7VAA06a2D98NQOcTh5O9SeFMSFzt8K6-lrh49KxAz4ZVOtOdOMj-WdOoCzhodbjsIJKk9peZYOHIFjieH5CMRKdVxj3wJ9wYPNgK7n43o5vbIw_Oj9fW60HWj2o2mnfKAipPKSQG_x8Pbq9JuuPamyur6aYFfbykvynaNb2EhjsC-EQw' -H 'Accept-encoding: txt' -H 'Accept: */*' -H 'Connection: keep-alive'

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:




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":

curl -XPOST -H 'Authorization: Basic MWEyYjNjNGQ1ZTZmN2E4YjljMGQxZTJmM2E0YjVjNmQ6WnRIZjVhd2xGdmtWRUpYMzlrRzZtR1UxalpBemxDbGhUcDREZ3NVTQ==' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Host:' -d 'grant_type=refresh_token&code=tleSIsInN1YiI6/y1LIF1p==' ''

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:
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. - 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.