Authress implementation of x25519 access keys
How do Authress keys work?โ
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โ
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
.
{
"keys": [
{
"kid": "pve478iGSx8W2gszzQYmkT",
"alg": "EdDSA",
"kty": "OKP",
"crv": "Ed25519",
"x": "YC2bfzWMHVIDZtiRn4GF-olNkoTtLUm3V7ldS3FviLo"
}
]
}
JWT generationโ
Using the private key the SDK generates a signed 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;
};
{
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:
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.
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โ
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:
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.
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.
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;
}