Skip to main content

Authress implementation of x25519 access keys

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โ€‹

[Advanced] 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 auth.yourdomain.com, then Authress would host your private keys at https://auth.yourdomain.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://auth.yourdomain.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:

[Advanced] 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;
};
The service client signed JWT
{
aud: "acc-authress-account-id",
iss: "https://auth.yourdomain.com/v1/clients/sc_service_client_id",
sub: "sc_service_client_id",
iat: 1704809699,
exp: 1704813299,
scope: "openid"
}

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.

[Advanced] 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 auth.yourdomain.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')
}
}

Static key usageโ€‹

caution

In most circumstances Authress recommends against exposing the private access key outside of the service's production runtime. The fewer places the key is exposed to the fewer opportunities it has to be compromised. When possible use one of the Authress SDKs to convert the secret access key to a JWT, otherwise please use the static secret access key and private key with care.

It isn't always the case that you can pass the API Access key to one of our SDKs to generate the necessary JWT. There are two situations where that frequently happens:

  • OAuth delegation / authentication / token requests
  • SaaS provider integrations

With these flows, usually a static secret string will have to be passed to the provider or process and that needs to work. For that reason the secret access keys generated by Authress actually provide this as well. By generating a secret access key for a service client, the access key can be used as a Bearer token in the Authorization header requests.

Your users would use the static secret in your SDK or a curl call to interact with your API:

Using the static service client access key directly
curl https://application.yourdomain.com -H"Authorization: Bearer sc_aaa.acc_001.secret"

Then on your service side you would need to verify the incoming secret. This would automatically get verified when passed to Authress, or it can be verified directly.

info

When possible use the Authress SDK. An example of using the Authress SDK to handle access key verification is in the Service Client access token api request verification section.

[Advanced] Verifying a service client access key without the Authress SDK
import { createPrivateKey } from 'crypto';
import { jwtVerify, importJWK } from 'jose';
import axios from 'axios';

async function verifyPrivateAccessKey(usersPrivateAccessKey)
// Private access keys generated by Authress have the following form:
const [clientId, keyId, _, x25519PrivateKey] = accessKey.split('.');

// Fetch the public key saved in Authress using your Authress account custom domain
const authressCustomDomain = 'auth.yourdomain.com';
const serviceClientPublicKeyUrl = `https://${authressCustomDomain}/v1/clients/${clientId}/.well-known/openid-configuration/jwks`;
const publicKeysResult = await axios.get(serviceClientPublicKeyUrl);
const matchingPublicKey = publicKeysResult.data.keys.find(key => key.kid === keyId);

// Generate a verifiable token
const verifiableToken = await new SignJWT({})
.setProtectedHeader({ alg: 'EdDSA' })
.sign(createPrivateKey({
key: Buffer.from(x25519PrivateKey, 'base64'), format: 'der', type: 'pkcs8'
}));

try {
const isVerified = await jwtVerify(verifiableToken, await importJWK(matchingPublicKey), { algorithms: ['EdDSA'] });
return !!isVerified;
} catch (error) {
return false;
}