Skip to main content

API and Access keys

This article provides a deeper dive into how Service Client access keys work, and how to effectively use them. As we know from the service client KB article, there are a number of different scenarios where API keys or access keys are necessary. Included in the article is also how to assign permissions and access control for those service clients.

Here we'll discuss the flow as well as how Authress generates the access keys that you and your customers use to authenticate.

Generating service client JWTs

Either you are generating service clients and access keys for inter-service communication, or generating API keys for your users to access your service's APIs. We'll use Service Client or API Key interchangeably below. Specifically:

  • Service Client is the service
  • API Key is the key the service client uses to identify itself.

In the case you are providing a library or a CLI, the service client would be the developer or user, and they would pass the api key to the library or CLI at runtime.

Authress uses asymmetric access keys which are a Public/Private pair. The Public key lives in Authress and the Private key is generated and sent to the User or Developer for embedding in their service. Authress uses as asymmetric signatures because they are much more secure than a shared secret. For the generation of access keys for your users:

  1. The user authenticates into your service using their identity provider
    • In the case you are using Authress they would use their SSO identity
  2. After logging in, you would know which customer account they have access to
  3. The user would use your portal to request a new api key
    • On request your service would make an API call to Authress to generate a Service Client and an associated access key
  4. Return the access key back to the user
  5. The user would embed the access key in your SDK to make calls to your API

How it works in Authress

The first interaction with Authress is the generation of the public/private key pair. When generated, the public key in stored in the Authress database, and the private key is returned to the user. The user takes their private key and stores it on their side. Authress recommends encrypting all secrets, and provides this AWS encryption example as a recommendation for how to do that.

Generating api key public private key pair

Then later the service client uses the private key to create a JWT that can be passed back to API and can be verified against the public key JWK that was created in the first step. The JWT will be passed through your SDK to your API, verified and then sent to Authress for an authorization access check.

Authorizing the private key JWT

[Later] When the access token expires the client can generate a new JWT, the Authress SDK does this automatically. This avoids all the issues that are generated by using the common flat api key as well as increases security for both the application API as well as the client caller.

Building a client SDK

Because you'll be giving your users the access key generated by Authress, you'll need a way to generate JWTs from this access key. The easiest way to do this to embed the Authress SDK in your distributed SDK, because it already supports Private key => JWT creation. Here is an example snippet to place directly in your distributed SDKs, one exists for every language we support.

(Other SDKs are available in all the other languages on the Authress API documentation page)

Install Authress SDK (nodejs)
npm install authress-sdk
Your client SDK (or CLI)
const { ServiceClientTokenProvider } = require('authress-sdk');

// Configure the custom domain: https://authress.io/app/#/settings?focus=domain
const authressCustomDomainUrl = 'https://login.company.com';

class myApplicationServiceClient {
// The customersSecretAccessToken is the private key you generated from Authress
// to give to your customer as an API key.
// Generate these by creating a service client and access key at:
// https://authress.io/app/#/api?route=post-/v1/clients/-clientId-/access-keys
async sdkApiCall() {
const tokenProvider = new ServiceClientTokenProvider(
customersSecretAccessToken, authressCustomDomainUrl);
const token = await tokenProvider.getToken();
const headers = { Authorization: `Bearer ${token}` };
return httpClient.get(url, headers);
}
}

How do Authress keys work?

info

This section specifically calls out how our access keys work. This is a deep technical topic and only provides insight into the security practices enabled for your account. It is not required understanding for the use of Authress.

Authress generates ed25519 asymmetric public and private key pairs and uses the EdDSA algorithm to generate JWT signed tokens. The service clients Authress creates enables the usage of up to 5 simultaneous private keys. Authress uses this over RSA for a number of reasons including the strength of security relative to key size and the over required key size. EdDSA is more secure.

In the case you want to generate your own Authress compatible keys, the easiest way to do that would be generate the pair and upload the public key:

Key generation

Generate a key pair
const { promisify } = require('util');
const { generateKeyPair, createPublicKey } = require('crypto');
const generateKeyPairAsync = promisify(generateKeyPair);

const clientKeyPair = await generateKeyPairAsync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'der' },
privateKeyEncoding: { type: 'pkcs8', format: 'der' }
});
const keyId = 'KID';
const accessKey = {
// Stored in Authress
publicKey: clientKeyPair.publicKey.toString('base64')
// Store locally
privateKey: clientKeyPair.privateKey.toString('base64')
};

Public key storage

Once Authress has knowledge of the cryptographic public key, it hosts these public keys on the public endpoint. If your Custom Domain is login.company.com, then Authress would host your private keys at https://login.company.com/.well-known/openid-configuration/jwks.

You can see the Authress Portal keys available at Authress sample public keys. (You will notice that these keys are in the JWK JSON format instead of the PEM string format.)

Service clients get their own dedicated list of JWK public keys which are hosted at specific location. For the service client sc_001 the keys are stored at https://login.xemaples.com/v1/sc_001/.well-known/openid-configuration/jwks.

Sample public key response from /jwks endpoint
{
"keys": [
{
"kid": "pve478iGSx8W2gszzQYmkT",
"alg": "EdDSA",
"kty": "OKP",
"crv": "Ed25519",
"x": "YC2bfzWMHVIDZtiRn4GF-olNkoTtLUm3V7ldS3FviLo"
}
]
}

JWT generation

Using the private key the SDK generates a signed JWT:

Generate service client JWT
// Source https://www.npmjs.com/package/authress-sdk
const { SignJWT } = require('jose');
const { createPrivateKey } = require('crypto');

async function(accessKey) {
const accountId = accessKey.split('.')[2];
const decodedAccessKey = {
clientId: accessKey.split('.')[0],
keyId: accessKey.split('.')[1],
audience: `${accountId}.accounts.authress.io`,
privateKey: accessKey.split('.')[3]
};

const now = Math.round(Date.now() / 1000);
const jwt = {
aud: decodedAccessKey.audience,
iss: `https://${authressCustomDomain}/v1/clients/${decodedAccessKey.clientId}`,
sub: decodedAccessKey.clientId,
iat: now,
exp: now + 60 * 60,
scope: 'openid'
};

const importedKey = createPrivateKey({
key: Buffer.from(decodedAccessKey.privateKey, 'base64'), format: 'der', type: 'pkcs8'
});

const signedJWT = await new SignJWT(jwt).setProtectedHeader({
alg: 'EdDSA', kid: decodedAccessKey.keyId, typ: 'at+jwt'
})
.sign(importedKey);

return signedJWT;
};

JWT verification

Now that we know how we are generating the key pair and the JWT, we need to verify the incoming JWTs. Doing this is fairly straightforward, but still requires a number of important security based aspects. The curtailed version of the JWT verification looks like this:

info

Note This code snippet is not the full validation, using only this validation is not sufficient to keep your API secure, the full implementation is available in the Authress SDKs and below is part of the NodeJS implementation.

Verify JWT token
import { jwtVerify, importJWK } from 'jose';

async function verifyTokens(token) {
// The issuer is found in the JWT in the `iss` claim property
// * And it should match your Authress custom domain login.company.com
const issuer = parsedJwt.iss;
const key = await getPublicKey(`${issuer}/.well-known/openid-configuration/jwks`, kid);

try {
const pemKey = await importJWK(key);
const options = { algorithms: ['EdDSA'], issuer };
const verifiedToken = await jwtVerify(token, pemKey, options);
return verifiedToken.payload;
} catch (verifierError) {
throw Error('Unauthorized')
}
}