Configuring OIDC with your own API

Use OpenID Connect within your tasks to authenticate with your own API.
OpenID Connect (OIDC) allows your tasks to access your own API without needing to store API credentials. At a high level, your API will verify incoming ID tokens are issued by Airplane and then extract the token's claims. The token's claims contain metadata about how the token was generated, such as the task it's coming from and the email of the runner.
These claims can be used to implement permissions by rejecting requests that don't match your criteria, for example, and you can include these claims in your application's audit logs.
This guide will walk you through an example of how to configure your API to use Airplane OIDC tokens. See Airplane OpenID Connect provider for an overview of how to generate tokens and the token format.

Passing ID tokens to your API

Tokens can be generated at runtime inside your tasks using the Airplane SDK and passed to your API via (e.g.) an HTTP header.
typescript
Copied
1
import airplane from "airplane";
2
3
export default airplane.task(
4
{
5
slug: "generate_oidc_token",
6
},
7
async (params) => {
8
const token = await airplane.auth.idToken("https://your-app.example.com");
9
10
// Attach the token as a header when calling your API.
11
fetch("https://your-app.example.com/endpoint", {
12
method: "POST",
13
headers: { Authorization: `Bearer ${token}` },
14
});
15
},
16
);
For more information on how to generate tokens, see Generating tokens.

Verifying the ID token

Once your API receives the ID token, you must verify the token is correctly signed by Airplane. We recommend doing so with a standard JWT library that matches your stack. You'll typically want to verify the token's signature, issuer, and audience. We recommend doing this in e.g. API middleware to more broadly protect the relevant API endpoints.
Once you've verified that the token is signed correctly, you can decode the token to get access to the token's claims. You'll always want to check team_id claim matches the ID of your team (team ID can be found in Team Settings).
You will need to install jsonwebtoken and jwks-rsa.
typescript
Copied
1
import * as jwt from "jsonwebtoken";
2
import * as jwksRsa from "jwks-rsa";
3
4
const verifyToken = async (token: string): Promise<string | jwt.JwtPayload> => {
5
const jwksClient = new jwksRsa.JwksClient({
6
// Found via Airplane's OIDC provider settings.
7
jwksUri: "https://api.airplane.dev/.well-known/jwks",
8
});
9
const signingKey = await jwksClient.getSigningKey();
10
11
const claims = jwt.verify(token, signingKey.getPublicKey(), {
12
// Audience specified when generating the token.
13
audience: "https://your-app.example.com",
14
// Check the issuer is Airplane.
15
issuer: "https://api.airplane.dev",
16
});
17
18
// Verify the token is for your Airplane team.
19
if (claims.team_id != "$YOUR_TEAM_ID") {
20
throw new Error("Invalid team ID");
21
}
22
23
// e.g. claims.task_slug contains the slug of the task that generated the token.
24
return claims;
25
};
Here's a non-exhaustive list of third-party libraries you can use to verify OIDC tokens:
Airplane OIDC provider's public keys are available at https://api.airplane.dev/.well-known/jwks.
We strongly recommend using a third-party library for token verification. Doing so greatly reduces the risk of a security vulnerability in your code.
Your API may want to perform additional checks on the token. For example:
  • The env_slug claim can be used to gate certain API operations depending on the environment. For example, you may want to only have write access to a production database from the prod environment.
  • The runner_email claim can be used to ensure your API is only being called by a specific user.
typescript
Copied
1
// Check that only the user "test@company.com" is allowed to access your API.
2
if (claims["runner_email"] != "test@company.com") {
3
throw new Error("Invalid user");
4
}
For the full set of token claims, see Token payload reference.

Example: Integrating Airplane OIDC as middleware in your API

Here, we show an example of how to integrate Airplane OIDC as middleware in your Express app. You will need to install express, jsonwebtoken, and jwks-rsa.
javascript
Copied
1
import * as jwt from "jsonwebtoken";
2
import * as jwksRsa from "jwks-rsa";
3
4
// You will need to add validateAirplaneToken to your Express.js middleware stack.
5
//
6
// const app = express();
7
//
8
// app.use(validateAirplaneToken);
9
10
function validateAirplaneToken(req, res, next) {
11
// Assumes the Airplane ID token is passed in the Authorization header.
12
const idToken = req.headers.authorization.split(" ")[1];
13
14
if (!idToken) {
15
return res.status(401).json({ error: "Authorization header not found" });
16
}
17
18
try {
19
const claims = verifyToken(idToken);
20
} catch (e) {
21
return res.status(401).json({ error: "Unable to validate token claims" });
22
}
23
24
// At this point, the token is valid. You can add the claims to the request object
25
// for use in your API.
26
//
27
// e.g. you can reference `req.airplaneTokenClaims.runner_email` in your API
28
// handlers to check which users can perform certain operations.
29
req.airplaneTokenClaims = claims;
30
31
next();
32
}
33
34
const verifyToken = async (token): => {
35
const jwksClient = new jwksRsa.JwksClient({
36
jwksUri: "https://api.airplane.dev/.well-known/jwks",
37
});
38
const signingKey = await jwksClient.getSigningKey();
39
40
const claims = jwt.verify(token, signingKey.getPublicKey(), {
41
// Audience specified when generating the token.
42
audience: "https://your-app.example.com",
43
// Check the issuer is Airplane.
44
issuer: "https://api.airplane.dev",
45
});
46
47
// Verify the token is for your Airplane team.
48
if (claims.team_id != "$YOUR_TEAM_ID") {
49
throw new Error("Invalid team ID");
50
}
51
52
// e.g. claims.task_slug contains the slug of the task that generated the token.
53
return claims;
54
};