Skip to content

Conversation

@zeucapua
Copy link

@zeucapua zeucapua commented Jan 29, 2026

Implements AT Protocol OAuth using @atproto/api and @atproto/oauth-client-node. The form allows users to login with a handle from the app, create an account through selfhosted.social, or go through Bluesky.

npmx-oauth.mp4

Notes:

  • requires NUXT_SESSION_PASSWORD env variable to encrypt cookies
  • login will fail on preview due to the redirect URIs not matching the generated preview URL

@vercel
Copy link

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Jan 30, 2026 0:35am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Jan 30, 2026 0:35am
npmx-lunaria Ignored Ignored Preview Jan 30, 2026 0:35am

Request Review

@43081j
Copy link
Collaborator

43081j commented Jan 29, 2026

just a couple of minor comments but looks good to me, pretty straightforward! nice work 🎉

Copy link
Contributor

@jonathanyeong jonathanyeong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

})

const response = await fetch(
`${SLINGSHOT_ENDPOINT}/xrpc/com.bad-example.identity.resolveMiniDoc?identifier=${agent.did}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my knowledge, why does the endpoint contain bad-example?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fig lol. XRPC's need a nsid to be complete and they used their domain for it

https://atproto.com/specs/xrpc#lexicon-http-endpoints

Copy link
Contributor

@fatfingers23 fatfingers23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! all the atproto stuff is atprotoing

'api.bitbucket.org', // Bitbucket API
'codeberg.org', // Codeberg (Gitea-based)
'gitee.com', // Gitee API
//microcosm endpoints for atproto data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
//microcosm endpoints for atproto data
// microcosm endpoints for atproto data

export const OAUTH_SESSION_CACHE_STORAGE_BASE = 'oauth-atproto-session'

export class OAuthSessionStore implements NodeSavedSessionStore {
//TODO not sure if we will support multi accounts, but if we do in the future will need to change this around
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
//TODO not sure if we will support multi accounts, but if we do in the future will need to change this around
// TODO: not sure if we will support multi accounts, but if we do in the future will need to change this around

}
deleteCookie(this.event, this.cookieKey)
}
}
Copy link
Contributor

@Kai-ros Kai-ros Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a factory at the bottom to clean up the API a bit?

export const useOAuthStorage = (event: H3Event) => {
  return {
    states: new OAuthStateStore(event),
    sessions: new OAuthSessionStore(event),
  }
}

That way when it gets invoked elsewhere, like you do above in server/api/auth/atproto.get.ts, it could be as simple as const storage = useOAuthStorage(event).

if (!process.env.NUXT_SESSION_PASSWORD) {
throw createError({
status: 500,
message: 'NUXT_SESSION_PASSWORD not set',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Extract to constants file

Suggested change
message: 'NUXT_SESSION_PASSWORD not set',
message: UNSET_NUXT_SESSION_PASSWORD,

if (!process.env.NUXT_SESSION_PASSWORD) {
throw createError({
status: 500,
message: 'NUXT_SESSION_PASSWORD not set',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Extract to constants file

Suggested change
message: 'NUXT_SESSION_PASSWORD not set',
message: UNSET_NUXT_SESSION_PASSWORD,

if (!process.env.NUXT_SESSION_PASSWORD) {
throw createError({
status: 500,
message: 'NUXT_SESSION_PASSWORD not set',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Extract to constants file

Suggested change
message: 'NUXT_SESSION_PASSWORD not set',
message: UNSET_NUXT_SESSION_PASSWORD,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add validation here since we're stepping outside of the system, the query params and a third-party API. Casting these with as could hide potential runtime crashes if for whatever reason the PDS or Slingshot return unexpected shapes.

  1. AuthRequestSchema or AuthQuerySchema - For the validate getQuery(event)
  2. MiniDocSchema - For the fetch response from the Slingshot endpoint

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand it, this will be exposed to the public via the ATProto network? Would be nice to validate the output with a schema here as well. Just to guard against breakage for the OAuth handshake with an invalid URL or missing field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add a validation schema here we get the benefit of not having to perform repeat manual checks for NUXT_SESSION_PASSWORD and having a SessionDataSchema parse the session.data ensures the FE gets a reliable shape. Bonus is we won't accidentally leak any internal session metadata added down the line.

@danielroe
Copy link
Collaborator

danielroe commented Jan 30, 2026

we can ignore the provenance warnings btw

Copy link
Contributor

@Kai-ros Kai-ros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my above comments on validation. Regardless, great work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants