Skip to content

Commit b77634f

Browse files
mjunaidcaclaude
andcommitted
fix(sso): accept JWT bearer token in members endpoint
The /api/organizations/{id}/members endpoint only supported session cookies. TaskFlow API forwards JWT bearer tokens for API-to-API auth. Now supports both: 1. Session cookies (browser requests via OrgSwitcher) 2. JWT Bearer token (API requests from TaskFlow workers endpoint) Extracts user ID from JWT 'sub' claim when no session is present. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3e7eaa3 commit b77634f

1 file changed

Lines changed: 30 additions & 3 deletions

File tree

  • apps/sso/src/app/api/organizations/[orgId]/members

apps/sso/src/app/api/organizations/[orgId]/members/route.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
*
44
* Used by TaskFlow API to sync org members as workers.
55
* Requires the user to be a member of the organization.
6+
*
7+
* Supports two auth methods:
8+
* 1. Session cookies (for browser requests)
9+
* 2. JWT Bearer token (for API-to-API requests from TaskFlow)
610
*/
711

812
import { auth } from "@/lib/auth";
@@ -11,16 +15,39 @@ import { NextResponse } from "next/server";
1115
import { db } from "@/lib/db";
1216
import { member, user } from "@/lib/db/schema-export";
1317
import { eq, and } from "drizzle-orm";
18+
import { decodeJwt } from "jose";
1419

1520
export async function GET(
1621
request: Request,
1722
{ params }: { params: Promise<{ orgId: string }> }
1823
) {
24+
const headersList = await headers();
25+
let userId: string | null = null;
26+
27+
// Try session auth first (browser requests)
1928
const session = await auth.api.getSession({
20-
headers: await headers(),
29+
headers: headersList,
2130
});
2231

23-
if (!session) {
32+
if (session) {
33+
userId = session.user.id;
34+
} else {
35+
// Try JWT Bearer token (API-to-API requests)
36+
const authHeader = headersList.get("authorization");
37+
if (authHeader?.startsWith("Bearer ")) {
38+
try {
39+
const token = authHeader.slice(7);
40+
const payload = decodeJwt(token);
41+
// JWT 'sub' claim contains the user ID
42+
userId = payload.sub as string || null;
43+
console.log("[Members API] JWT auth - user:", userId);
44+
} catch (e) {
45+
console.error("[Members API] JWT decode failed:", e);
46+
}
47+
}
48+
}
49+
50+
if (!userId) {
2451
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
2552
}
2653

@@ -31,7 +58,7 @@ export async function GET(
3158
const userMembership = await db.query.member.findFirst({
3259
where: and(
3360
eq(member.organizationId, orgId),
34-
eq(member.userId, session.user.id)
61+
eq(member.userId, userId)
3562
),
3663
});
3764

0 commit comments

Comments
 (0)