Key Generation
generate-safe-id is fine. For a secret API key, UUIDs are less fine. Also fine: directly reading 16-32 bytes from crypto.getRandomBytes and converting to hex.
Key vs Secret
An AWS_Access_Key is basically just a random username. If you understand how userids work in your system, you don't have to worry about this.
Key Usage
Don't use JWT; JWT is a mess. The real distinction here isn't what a secret key can get you, but whether you attempt to track clients without serverside state. What JWT ostensibly gets you is an arbitrary per-client state store without requiring database lookups on the serverside. In reality, you almost never get that, and inherit all the downsides of JWT --- including extreme, scary complexity --- for very little practical benefit. If you're already using a database to handle client request (like most applications), keep it simple.
Key Storage (Clientside)
Cookies are safer than localStorage; modern browsers are bristling with defenses for cookies, but not for localStorage access.
Key Storage (Serverside)
We hash passwords because passwords are valuable across sites; it's a big deal to compromise someone's password, even on a random low-value application. That's not true of API keys. If your database is compromised, the API keys don't matter anymore. Don't bother encrypting them.
Authorization
Authorization is a big topic. API keys are an authentication concern. AuthN != AuthZ. Keep it simple with AuthN.
Don't:
* Bother with TLS client certificates. Client certs are useful internally, when you have an ensemble of services that need to talk to each other securely. They're much less useful when you have tens of thousands of semi-anonymous clients. They're a huge pain in the ass to deal with on both the client and the server side, and have extremely bad UX.
* Use JWT or Macaroons. If you can get away with opaque random strings and a serverside database, then get away with that, for as long as you possibly can. We work on applications running at "popular Internet app" scales and they manage this just fine. Trying to push client state to the client costs security; it's something you get away with, not something you do to shore up your defenses.
* Bother with OAuth. The only "interesting" thing OAuth provides is a UX to allow you to delegate authentication to third-party services --- think, every app that wants to see your Twitter timeline. If you don't have that problem, OAuth doesn't buy you anything except complexity.