feat: added helper methods for storing and retrieving "AuthRequest" objects in KV
Description
This PR introduces two new helper methods, storeAuthRequest and getAuthRequest, to the OAuthHelpers object. These methods provide developers with the ability to manually store and retrieve AuthRequest objects from the KV store, offering greater flexibility in managing the authorization flow.
This is particularly useful in scenarios where the authorization process is split across different contexts or invocations, allowing developers to persist and retrieve the authorization state as needed.
Example usage
// In one part of your application (e.g., initial request)
const authRequestId = 'some-unique-id';
const authRequest = {
responseType: 'code',
clientId: 'your-client-id',
// ... other properties
};
await env.OAUTH_PROVIDER.storeAuthRequest(authRequestId, authRequest);
// In another part of your application (e.g., a callback handler)
const storedRequest = await env.OAUTH_PROVIDER.getAuthRequest(authRequestId);
if (storedRequest) {
// Continue the authorization flow
}
Real world example
app.get(`/oauth/authorize`, async (c) => {
const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw)
oauthReqInfo.scope = Object.keys(scopes)
if (!oauthReqInfo.clientId) {
return c.text('Invalid request', 400)
}
// Generate UUID to prevent CSRF attacks
const authRequestId = crypto.randomUUID();
const { authUrl, codeVerifier } = await getAuthorizationURL({
client_id: c.env.CLIENT_ID,
redirect_uri: new URL('/oauth/callback', c.req.url).href,
state: authRequestId,
scopes,
})
// Store state: { codeVerifier, ...oauthReqInfo } in KV
c.env.OAUTH_PROVIDER.storeAuthRequest(authRequestId, {
clientId: oauthReqInfo.clientId,
redirectUri: oauthReqInfo.redirectUri,
codeVerifier,
})
return Response.redirect(authUrl, 302)
})
app.get(`/oauth/callback`, zValidator('query', AuthQuery), async (c) => {
const { state, code } = c.req.valid('query')
// "state" was set to authRequestId
const oauthReqInfo = await c.env.OAUTH_PROVIDER.getAuthRequest(state)
if (!oauthReqInfo.clientId) {
throw new McpError('Invalid State', 400)
}
const [{ accessToken, refreshToken, user, accounts }] = await Promise.all([
getTokenAndUserDetails(c, code, oauthReqInfo.codeVerifier),
c.env.OAUTH_PROVIDER.createClient({
clientId: oauthReqInfo.clientId,
tokenEndpointAuthMethod: 'none',
}),
])
// Return back to the MCP client a new token
const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({
request: oauthReqInfo,
userId: user.id,
metadata: {
label: user.email,
},
scope: oauthReqInfo.scope,
props: {
type: 'user_token',
user,
accounts,
accessToken,
refreshToken,
} satisfies AuthProps,
})
return Response.redirect(redirectTo, 302)
})
🦋 Changeset detected
Latest commit: b313a3eba893f87b26fb824461da5796e5a25d85
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| @cloudflare/workers-oauth-provider | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
npm i https://pkg.pr.new/cloudflare/workers-oauth-provider/@cloudflare/workers-oauth-provider@95
commit: b313a3e
I agree that the core responsibility for securely storing and validating the state parameter lies with the OAuth client, not the server. You’re right that by handling this inside the provider library, we blur that boundary and risk encouraging insecure or misleading usage patterns.
The motivation behind this PR was mainly to simplify things for proxy-style applications (where the same app acts as both client and server), but I see how that use case probably falls outside the intended scope of workers-oauth-provider.
My thinking was that such applications could store the auth request in KV, while using PKCE to store the code_verifier in a secure cookie. Then, on the callback, we could validate the code_verifier to ensure that even if someone somehow obtained the state value, they wouldn’t be able to hijack the flow without also having access to the cookie. This seemed like a reasonable balance between simplicity and security for those hybrid setups.
That said, I agree that this pattern is probably better handled by applications themselves rather than baked into the library. I think it makes sense to close this PR and instead document best practices for “OAuth proxy” setups - for example, explaining that such applications should handle state persistence themselves (e.g., via cookies, localStorage, or a client-managed store) and that workers-oauth-provider will not do that for them.
If that sounds reasonable, I can draft some docs or an example to clarify this pattern instead.