DEV Community

Discussion on: Critique My Plan: API Key for Authentication

Collapse
 
tom profile image
tom • Edited

Disclaimer: this isn’t my specialism but I’ve been working on my security chops and took this opportunity to dive deep into this particular area. I hope someone more experienced & knowledgable can correct me if I’ve made mistakes!

You have some options here, and you also can dial your choices up based on how private the data you are storing is. It also depends on what you control and what is in the control of the game.

If you control the almost all the code on the client side — that is, you can configure how requests are made to your API — then you can use something relatively simple.

The words are overloaded, but I think the right nomenclature here is that you will give each user a token. Keys tend to used for signing and encrypting, but tokens carry an assertion of identity and capability that you will need to verify.

From what I understand, each user will have a username and password for your service, and they’ll need to log in before using your service from the game.

If so, you should be able to use a token written to an HTTP-only, secure cookie. You’d write the cookie when the user logs in to your service. When making requests from the game to your service, you’d use CORS with credentials enabled so that the cookies are included (developer.mozilla.org/en-US/docs/W...).

With this kind of design, the tokens must be protected from disclosure in storage and in transport. You’ll be relying on the browser for the former (same-origin policy) and HTTPS for the latter.

This is similar to the OAuth2 Bearer spec (tools.ietf.org/html/rfc6750) and the security recommendations from that spec hold true (tools.ietf.org/html/rfc6750#sectio...).

JSON web tokens (jwt.io/) are a pretty good standard for tokens, and you have the support of existing libraries for generation and verification.

The most important bit of the JWT is the secret, which will be the same for all of your tokens.

Your best bet for the secret is randomness from a cryptographically strong (pseudo-random) source: openssl is good for this (openssl rand -base64 32).

API Key vs API Secret

As I understand it, this is distinction is usually because an authentication protocol requires client-side cryptography, as in the OAuth1 specification (which is quite readable: oauth.net/core/1.0a).

Client-side cryptography is nearly always necessary but if you’re using HTTPS (and you should be) then it’s hidden away from you. OAuth1 was designed for an HTTPS-less world, so it’s all built into the protocol.

For your use-case I don’t believe you will need to provide API keys and secrets. It should be enough store tokens, users, roles, and a way to relate each to the others.

Token usage & policies

Your idea here is sensible, although keep in mind that, if you’re using JWT, you can encode the capabilities of the user in the token.

My only suggestion is that, if you want many-to-many relationships — many users with many different combinations of roles — that you keep the canonical list of roles separate from your user storage, and keep some kind of relation between the two.

Client-side storage

As mentioned above, I suggest a cookie, associated with the origin of your server (which must be secured with TLS). Since you’re operating in a relatively hostile environment — lots of user scripts — I suggest also making it HTTP-only, meaning that it cannot be read by JavaScript. You are relying on the same-origin policy in the browser to enforce that the key isn’t exposed to third-parties.

The auth0 docs are good for the pros & cons of various approaches to this: auth0.com/docs/security/store-tokens

Server-side storage

You won’t be storing the tokens server-side, but you will need to the secret in order to verify the token.

I suggest that you store it away from your user data, probably not even in a database, and you supply it to processes that need it via their environment. Cloud providers normally have tools for doing this (e.g. cloud.google.com/kms/docs/secret-m...).

Notes

Since you mentioned them: hashing and salting are used for storing information that must remain private even if the storage is compromised and that doesn’t need to be retrieved later but does need to be verified (like a password).

Base64 encoding is just a way of representing some underlying bytes. In this case, the library you link to (npmjs.com/package/generate-safe-id) uses a URL-safe base64 encoding of the random bytes it generates. Base64 is not a kind of encryption or hashing; it’s equivalent to plain-text and reversible, though of course you may base64 encode something that’s already encrypted or hashed.

My suggestion assumes that the game has a relatively permissive content-security policy (developer.mozilla.org/en-US/docs/W...) that will allow connections to your service’s origin.

This design is also not great if you intend to support other games, or if you have less control over the client-side that I have assumed.

(Season's greetings!)

Collapse
 
iyedb profile image
iyedb

The cookie cannot be http only if you want to use it with CORS (which implies js)

Collapse
 
tom profile image
tom • Edited

Why so?

As I understand it, HttpOnly just means the cookie can't be read by JavaScript; the browser can still send it along with a cross-origin request.

Thread Thread
 
iyedb profile image
iyedb

But how would you send a cross origin request without js? A cross origin request is basically a xhr request to different server (not only the domain but even the port number makes it different btw) than the one serving the current page. If you mark the cookies as http only you can of course still send a cors request but you don't have access to the cookies so you can't send then along with your cors request.

Thread Thread
 
tom profile image
tom • Edited

What's missing from this picture is that the browser can read, and send, cookies even if JavaScript can't.

That's why HttpOnly cookies exists: they are only available within the HTTP request.

So, a cross-origin HTTP request made using an XMLHttpRequest (or the fetch API) can include cookies that the JavaScript itself can't read. This is referred to as "credentials".

With XMLHttpRequest:

// from https://a.com
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://b.com/c.json', true);
xhr.withCredentials = true;
xhr.send();

and fetch:

fetch('https://b.com/c.json', {
  credentials: 'include'
})

Hopefully that makes sense!

By the way, this system (cross-origin, secure, HTTP-only cookies) is how TweetDeck and Twitter Lite's authentication works against the Twitter API.

Thread Thread
 
iyedb profile image
iyedb

Makes perfect sense. But it's not possible to configure that request by for example adding a authorization header with the value of a cookie.

Thread Thread
 
tom profile image
tom

Good point!

Collapse
 
imthedeveloper profile image
ImTheDeveloper • Edited

Hi Tom,
Firstly thank you for the detailed response and efforts put into answering all of my questions. The time taken is appreciated!

To answer some of your assumptions made. I do not control the game code at all, however I can I inject anything post page render using my user script. This will allow me to control the way in which my API is accessed from the client, however I should probably make the assumption that anyone can see that code and make a request using whatever client e.g. postman.

I think it's interesting you mentioned JWT. I'm going to do some reading up on that vs oAuth2.

I think at the moment I'm toying with a key design decision:

  1. In the short term do I allow a user to generate a token from their user profile in my system (not the game) and they just then enter this in an injected form I place in the game. This can then be stored as a cookie with http only enabled as you suggested. The benefit here is mainly that I remove a bit of coding on my side for some aspects and also allows the user to revoke the token or regen quite easy. I see this route as being the easy/lazy route.

  2. Do I inject a form into the game where the user can enter their user/pass for my system tools, which in turn returns the packaged token and saves it within an encrypted cookie with http only again. This feels a bit more user friendly but also a bit more overhead on my implementation. I'm wondering since I have only the user script to access data (via javascript) whether I can even access a http only cookie. I assume you need to read the cookie to send it in the subsequent API calls? Or do you send the cookie in the request and the server reads it?

Alternatively I just store it in the web storage.

jwt flow
Source: auth0.com/docs/jwt

I've actually just read up on jwt some more on auth0 and it's offered some great validation of your reply. I think I'll go with generating a JWT and then have this stored on the client in their local storage. I'll create a new API route for login/creation of the JWT in my sails.js app and then build out a policy that does the checks against the supplied JWT. I can then just drop this policy in against my user script routes since I generate the javascript on the fly that is injected into the game pages.

Collapse
 
tom profile image
tom • Edited

I should probably make the assumption that anyone can see that code and make a request using whatever client e.g. postman.

Absolutely make this assumption. It’s that which makes me suggest using an HTTP-only, secure cookie: it defends it from code that knows what it’s looking for and where to look.

I think it's interesting you mentioned JWT. I'm going to do some reading up on that vs oAuth2.

This isn’t so much of a “vs” as two separate (complimentary) ideas. JWT is just a standard for creating and validating tokens. OAuth2 is a set of authentication protocols.

JWT is a totally logical choice for a token within an OAuth2 flow.

It’s not explicitly mentioned in the JWT docs but the recommended use of a JWT is in the Authorisation header, which is the same recommendation as in the OAuth2 Bearer spec (tools.ietf.org/html/rfc6750#sectio...).

I’m toying with a key design decision…

I won’t suggest anything specific but if there’s going to be a login form for the service, and an embedded login form, could they be the same form? (think iframes, CSP)

I'm wondering since I have only the user script to access data (via javascript) whether I can even access a http only cookie. I assume you need to read the cookie to send it in the subsequent API calls? Or do you send the cookie in the request and the server reads it?

The first step is setting the cookie from the server, using the Set-Cookie header (developer.mozilla.org/en-US/docs/W...).

After that, the browser does all this for you when you either (1) set withCredentials = true on the XMLHttpRequest (developer.mozilla.org/en-US/docs/W...) or (2) set credentials: 'include' if you’re using the fetch API (developer.mozilla.org/en-US/docs/W...).

It slurps up all the cookies for the origin it’s talking to and sends them along with the request automatically.

You don’t need your client side to be able read the cookie at all.

One other thing you could consider is writing a cookie that indicates that the user is logged in that can be read from JavaScript: that let’s you make decisions in your client-side code that don’t require “checking” with your API.

And I strongly suggest you don’t use local storage: you will have to write it against the origin of the game which makes it accessible to any and all other user scripts running.