Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/auth/auth-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@ export function protectedResourceHandler({
authServerUrls: string[];
}) {
return (req: Request) => {
const origin = new URL(req.url).origin;
const resourceUrl = new URL(req.url);

resourceUrl.pathname = resourceUrl.pathname
.replace(/^\/\.well-known\/[^\/]+/, "");

// The URL class does not allow for empty `pathname` and will replace it
// with "/". Here, we correct that.
const resource = resourceUrl.pathname === '/'
? resourceUrl.toString().replace(/\/$/, '')
: resourceUrl.toString();

const metadata = generateProtectedResourceMetadata({
authServerUrls,
resourceUrl: origin,
resourceUrl: resource,
});

return new Response(JSON.stringify(metadata), {
Expand All @@ -43,13 +52,15 @@ export function protectedResourceHandler({
}

/**
* Generates protected resource metadata for the given auth server urls and
* resource server url.
* Generates protected resource metadata for the given auth server URLs and
* protected resource identifier. The protected resource identifier, as defined
* in RFC 9728, should be a a URL that uses the https scheme and has no fragment
* component.
*
* @param authServerUrls - Array of issuer URLs of the authorization servers. Each URL should
* match the "issuer" field in the respective authorization server's
* OAuth metadata (RFC 8414).
* @param resourceUrl - URL of the resource server
* @param resourceUrl - the protected resource identifier
* @param additionalMetadata - Additional metadata fields to include in the response
* @returns Protected resource metadata, serializable to JSON
*/
Expand Down
50 changes: 50 additions & 0 deletions tests/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, expect } from "vitest";
import { protectedResourceHandler } from "../src/index";

describe("auth", () => {
describe("resource metadata URL to resource identifier mapping", () => {
const handler = protectedResourceHandler({
authServerUrls: ["https://auth-server.com"],
});

const testCases = [
// Default well-known URI suffix (oauth-protected-resource)
{
resourceMetadata: 'https://resource-server.com/.well-known/oauth-protected-resource',
resource: 'https://resource-server.com',
},
{
resourceMetadata: 'https://resource-server.com/.well-known/oauth-protected-resource/my-resource',
resource: 'https://resource-server.com/my-resource',
},
{
resourceMetadata: 'https://resource-server.com/.well-known/oauth-protected-resource/foo/bar',
resource: 'https://resource-server.com/foo/bar',
},
// Ensure ports work
{
resourceMetadata: 'https://resource-server.com:8443/.well-known/oauth-protected-resource',
resource: 'https://resource-server.com:8443',
},
// Example well-known URI suffix from RFC 9728 (example-protected-resource)
{
resourceMetadata: 'https://resource-server.com/.well-known/example-protected-resource',
resource: 'https://resource-server.com',
},
{
resourceMetadata: 'https://resource-server.com/.well-known/example-protected-resource/my-resource',
resource: 'https://resource-server.com/my-resource',
},
] as const;

testCases.forEach(testCase => {
it(`${testCase.resourceMetadata} → ${testCase.resource}`, async () => {
const req = new Request(testCase.resourceMetadata);
const res = handler(req);
const json = await res.json();
expect(json.resource).toBe(testCase.resource);
});
});
});
});