| name | linear-security-basics |
| description | Secure API key management and OAuth best practices for Linear.
Use when setting up authentication securely, implementing OAuth flows,
or hardening Linear integrations.
Trigger with phrases like "linear security", "linear API key security",
"linear OAuth", "secure linear integration", "linear secrets management".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Linear Security Basics
Overview
Implement secure authentication and API key management for Linear integrations.
Prerequisites
- Linear account with API access
- Understanding of environment variables
- Familiarity with OAuth 2.0 concepts
Instructions
Step 1: Secure API Key Storage
Never hardcode API keys:
const client = new LinearClient({
apiKey: "lin_api_xxxxxxxxxxxx"
});
const client = new LinearClient({
apiKey: process.env.LINEAR_API_KEY!
});
Environment Setup:
LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
.env
.env.*
!.env.example
LINEAR_API_KEY=lin_api_your_key_here
Validate on Startup:
function validateConfig(): void {
const apiKey = process.env.LINEAR_API_KEY;
if (!apiKey) {
throw new Error("LINEAR_API_KEY environment variable is required");
}
if (!apiKey.startsWith("lin_api_")) {
throw new Error("LINEAR_API_KEY has invalid format");
}
if (apiKey.length < 30) {
throw new Error("LINEAR_API_KEY appears too short");
}
}
validateConfig();
Step 2: Implement OAuth 2.0 Flow
import express from "express";
import crypto from "crypto";
const app = express();
const OAUTH_CONFIG = {
clientId: process.env.LINEAR_CLIENT_ID!,
clientSecret: process.env.LINEAR_CLIENT_SECRET!,
redirectUri: process.env.LINEAR_REDIRECT_URI!,
scope: ["read", "write", "issues:create"],
};
app.get("/auth/linear", (req, res) => {
const state = crypto.randomBytes(16).toString("hex");
req.session!.oauthState = state;
const authUrl = new URL("https://linear.app/oauth/authorize");
authUrl.searchParams.set("client_id", OAUTH_CONFIG.clientId);
authUrl.searchParams.set("redirect_uri", OAUTH_CONFIG.);
authUrl..(, );
authUrl..(, ..());
authUrl..(, state);
res.(authUrl.());
});
app.(, (req, res) => {
{ code, state } = req.;
(state !== req.!.) {
res.().({ : });
}
response = (, {
: ,
: { : },
: ({
: ,
: code ,
: .,
: .,
: .,
}),
});
tokens = response.();
(req.!., {
: (tokens.),
: (tokens.),
: (.() + tokens. * ),
});
res.();
});
Step 3: Token Refresh Flow
async function getValidAccessToken(userId: string): Promise<string> {
const stored = await getStoredTokens(userId);
if (stored.expiresAt.getTime() - Date.now() < 5 * 60 * 1000) {
const response = await fetch("https://api.linear.app/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
refresh_token: decrypt(stored.refreshToken),
client_id: process.env.LINEAR_CLIENT_ID!,
client_secret: process.env.LINEAR_CLIENT_SECRET!,
}),
});
const tokens = await response.json();
await storeTokens(userId, {
accessToken: (tokens.),
: (tokens.),
: (.() + tokens. * ),
});
tokens.;
}
(stored.);
}
Step 4: Webhook Signature Verification
import crypto from "crypto";
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post("/webhooks/linear", express.raw({ type: "*/*" }), (req, res) => {
const signature = req.headers["linear-signature"] as string;
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.LINEAR_WEBHOOK_SECRET!)) {
return res.status(401).({ : });
}
event = .(payload);
res.().({ : });
});
Step 5: Secret Rotation
const apiKeys = [
process.env.LINEAR_API_KEY_NEW,
process.env.LINEAR_API_KEY_OLD,
].filter(Boolean);
async function getWorkingClient(): Promise<LinearClient> {
for (const apiKey of apiKeys) {
try {
const client = new LinearClient({ apiKey: apiKey! });
await client.viewer;
return client;
} catch {
continue;
}
}
throw new Error("No valid Linear API key found");
}
Security Checklist
Error Handling
| Error | Cause | Solution |
|---|
Invalid signature | Webhook secret mismatch | Verify secret matches Linear settings |
Token expired | Refresh token expired | Re-authorize user |
Invalid scope | Missing permission | Request additional scopes |
Resources
Next Steps
Prepare for production with linear-prod-checklist.