-
Notifications
You must be signed in to change notification settings - Fork 409
pyfa.io Authentication Workflow
ESI integrations was a very big change going into May, as the XML / CREAST API was shut down by CCP in favor of this newer API. One of the big unknowns was "How to manage tokens". As most folks know, ESI uses the same EVE Single Sign-On (SSO) that CREST used. Unfortunately, OAuth SSO is geared more towards web applications where a server can keep a secret, and wan't very conducive to desktop applications. When pyfa first implemented CREST, it did so under two schemas: implicit authentication, where the user would log in for 20 minutes but not be able to get a refresh token; and allowing user to enter their own client details to use. The 20 minute log in limit was due to the inability for pyfa to ship it's secret key (because pyfa is a desktop application, there are no guarantees that this key would remain secret).
This presented a problem when moving over to an all-ESI system, specifically when migrating skill fetching over from XML to ESI - I didn't want to force folks to sign in every time they wanted to update their skills. But we also don't want to force them to register as a developer (which is a legal agreement) to be able to use their own client details.
The solution I have implemented is an authentication proxy, currently located at pyfa.io. Basically, whenever the client wants to log in to a character, or refresh a token (the only two actions that require developer secret keys), it first goes through the pyfa.io web application (whose sole purpose is to proxy this authentication while keeping it's secret a secret). The actual calls for data (ie: fetching skills) is handled with client-side HTTP requests once pyfa has a valid access token, and aren't proxied through our web application. The workflows are as follows:
-
(if using Server Method) pyfa starts a webserver on a random port on the host machine - this is to allow the web application to report access token details back to the client
-
pyfa opens a web browser and directs user to
https://pyfa.io/oauth/authorize
. -
The web server then redirects the user to EVE's SSO
-
After logging in, EVE SSO redirects back to pyfa's web app with an authentication code.
-
The webserver then uses it's client ID and secret, bundled with the authentication code, to fetch an access token and refresh token from the EVE SSO system. This ensures that we can keep pyfa's client secret a secret.
6a. (if using Server Method) The web application then sends the information needed back to the pyfa client (after which the local web server shuts down)
6b. (if using Manual Method) The web application then displays information for the user to copy and paste into pyfa to save character login information.
- pyfa encrypts the refresh token with a key unique to that client and saves it in the database, along with the limited-time access token
Refreshing access token:
-
pyfa decrypts the refresh token using it's client key
-
pyfa HTTP POST's refresh token to pyfa's web server, which makes the call to EVE SSO to get a new access token
-
Web server responds to pyfa's request with a new access token and expiration time.
I've thought a lot about security and how to handle refresh tokens in this manner. Unfortunately, there isn't a 100% fool-proof way to secure refresh tokens when they are saved to the client, just like there isn't a 100% fool-proof way of securing the client secret, but there are steps that have been taken to mitigate any issues.
-
When user firsts logs into EVE SSO, pyfa generates a state token which the web server saves in the users session. Additionally, the webserver also generates a state token that goes to the EVE servers. When the user logs into EVE SSO, the state token is checked to make sure they match, and when pyfa gets the request back, it also checks it's own state token.
-
To get a new access token, pyfa needs to send the refresh token to the pyfa server. The webserver simply takes what it's given and authenticates it via EVE SSO. This means that if a refresh token is compromised, anyone can make a request to pyfa's server to retrieve an access token for the character (as long as the refresh token came from pyfa.io). To mitigate this, refresh tokens are encrypted with a key generated with each pyfa installation. This ensures that, even if the database is shared, other folks cannot access the refresh token. Additionally, when refreshing the access token, the refresh token is sent to pyfa's server over HTTPS connection, which will prevent sniffing out the plaintext token.
Security Advisaries:
-
When the option to do so is selected, pyfa starts a local webserver when logging into a character. This webserver is simple and bare bones, and does not support HTTPS. Thus, when pyfa's web application sends token information back to the client, it's sent back in plain text. Any packet sniffing application would be able to detect the refresh token in this instance (when doing the actual token refresh, that request is made over HTTPs since it's pyfa's web application that must support it). If this is something that concerns you, you have the option of having pyfa.io provide a block of text that you can then copy and paste into pyfa to get the token information onto the client.
-
If someone were to have their entire system compromised, the attacker would be able to find the clients encryption key, which would make it trivial to decrypt their refresh token. Having said that, this is true for most credentials normally stored on a computer. The only way around this that I know of would be to require a prompt for a passphrase to "unlock" the refresh tokens whenever ESI stuff is initialized. This could be cumbersome to the end user, so wasn't implemented, but if someone wants to make this an option I'm all for it.
-
The encryption key is stored in a file that ends in
.secret
in the user's save directory. This location will more likely be changing with an upcoming refactoring of where data is stored so that it's more out of the way and less easily copied by mistake.
There are a couple side effects to this workflow.
- If you use the same client on two different machines, you may have to log in to each character on each machine. This is because the SSO Characters are linked via pyfa's client secret hash, which should be different for each instance of pyfa
There are a few things that I would like to evaluate for future iterations of the ESI integrations:
-
Be able to provide your own client / secret again. The work that went into getting the proxy set up and whatnot did not allow time to make sure we also supported custom EVE Developer codesThis has been implemented. -
Apparently CCP have been working on an upgrade to the SSO system, that will allow desktop clients like pyfa to not have to worry about keeping their client details a secret. I'm not fully versed in this, but this whole song and dance that pyfa.io does might be alleviated at some point in the future :)