Cross-App Authentication on AT Protocol

We recently built cross-app authentication between Roomy and OpenMeet using AT Protocol service auth JWTs. The idea came from @erlend.sh (his writeup), @zicklag.dev introduced me to PDS service auth, and @meri.garden contributed reviews. I made the implementation in both apps: A user logs into Roomy, navigates to an OpenMeet event, and is silently authenticated and able to use their account on OpenMeet without a second login.

Full technical writeup: Cross-App Authentication on AT Protocol

I’m not a protocol designer or auth expert, and I want to make sure I’m not building on wrong assumptions.

  1. Is third-party consumption of PDS-signed tokens an intended pattern? The service auth discussion says PDS instances should not accept service auth from other services. I read that as PDS-to-PDS specifically. OpenMeet is a third-party app server consuming a PDS-signed JWT to verify identity. Bluesky’s chat service seems to do something similar (aud=did:web:api.bsky.chat#bsky_chat). I modeled my approach on that, just outside Bluesky’s service mesh. Am I reading this right?

  2. Should cross-app auth use a shared lxm or per-app identifiers? I use net.openmeet.auth as the lxm claim — a convention string in NSID format with no published lexicon. Bluesky uses published methods like chat.bsky.convo.getConvo. If other apps adopt this pattern, they’d each pick their own lxm. Would a shared community lexicon (e.g., community.lexicon.auth.exchangeServiceToken) make more sense, with aud differentiating the target service? Or is per-app NSID the right approach?

  3. aud format for non-PDS services. The permissions spec defines aud as “DID followed by required service type fragment” (e.g., did:web:api.example.com#svc_chat). I use did:web:api.openmeet.net with no fragment, and it works. But the spec suggests I should have one. Is a bare DID sufficient, or should I publish a service entry with a fragment like #openmeet_auth?

  4. Relationship to FedCM. There’s active work on FedCM support and an open PR for id_token_hint. My understanding is FedCM would let the browser mediate identity handoff natively, eliminating magic login links. It would also solve the ToS problem below, since FedCM’s client metadata includes terms_of_service_url shown before first sign-up. How should implementers building cross-app auth today plan for this transition?

  5. When should ToS consent happen? OpenMeet auto-creates an account when a new DID arrives via service auth, before the user hay have seen OpenMeet or agreed to its Terms of Service. FedCM handles this for browser flows (see Q4), but for API-only access where the user never visits OpenMeet directly, it’s unclear. Is there an emerging pattern for ToS consent when identity flows silently between apps?

Really curious to hear what folks think, and how others have approached it. Thanks for your time.

8 Likes

A user logs into Roomy, navigates to an OpenMeet event, and is silently authenticated and able to use their account on OpenMeet without a second login.

This isn’t clear to me. Are you saying that the exchange transfers the access token from one service to another? The “auto-authenticated” service doesn’t have any ability to make additional XRPC service calls for the authenticated identity without an access token that is scoped to the permissions it needs.

2 Likes

This comes at an interesting time, and you’ll see why shortly. I don’t think Service Auth credentials are intended to be used in this manner, sure you can do hand off from a web backend to the frontend (e.g., for direct to storage uploads where the backend holds the OAuth session).

However, doing this cross-application feels like a potential security risk, e.g., how does the application generating the token have the permissions to manage openmeet events, as to generate a service auth token that allows openmeet to manage events?

Service Auth has historically been from the perspective of calling an API on behalf of a user, not for mediating authentication in browser, so we should definitely consider if that change of context can be handled safely. Conceptually this is very similar to what OpenWebAuth does.

I think FedCM is probably the answer here, although it wouldn’t be completely seamless as this appears to be — you’d still get a browser prompt to sign-in, and possible get an oauth flow to approve additional scopes on your repo for that application.

Is this perhaps only working because OpenMeet isn’t necessarily storing data in your PDS (i.e., you only need identity, but not authorization).

I don’t think there’s currently anything FedCM for actually doing a cross-application hand off like this, though it is an interesting idea. I know Microsoft were working on something for like Teams where it embeds iframes from other sites, but I think that still encounters the issue of the account not having granted that application the right permissions yet.

(id_token_hint as far as I can tell is a work-around, I’m not sure if it’s actually needed long-term, it’s also not protocol standard field, it comes into the reference implementation because it has all of the OIDC properties implemented, like prompt, which also isn’t standard in AT Protocol yet)

4 Likes

Wait, how does this part work?

OpenMeet returns standard JWT access/refresh tokens

OpenMeet wouldn’t have the access/refresh tokens for the authenticated account’s PDS? Or is OpenMeet here actually issuing it’s own access/refresh tokens?

I think that’s what’s maybe making this discussion so confusing

2 Likes

No access tokens are transferred between services. Roomy never sends its PDS OAuth tokens to OpenMeet.

  1. User is logged into Roomy (has a PDS OAuth session)
  2. Roomy asks the user’s PDS to sign a short-lived service auth JWT (the PDS signs it with the user’s signing key)
  3. That JWT is sent to OpenMeet’s API with aud: did:web:api.openmeet.net and lxm: net.openmeet.auth
  4. OpenMeet verifies the JWT signature against the DID document (confirms identity)
  5. OpenMeet issues its own access/refresh tokens — completely independent of PDS tokens

The service auth JWT is just an identity proof, used once and discarded. Does that make sense?

2 Likes

Have to run, but OpenMeet is issuing tokens. I think you got it here. We’re just relying on identity being proven.

yeah, so I think you just need to be explicit that OpenMeet is an authorization server in this context, and that the exchange isn’t actually giving access to AT Protocol data from OpenMeet but rather it’s granting access to OpenMeet’s API.

3 Likes

I’m likely to be needing something similar soon, ie running an external service that receives a DID and needs a way to verify it came from an authenticated client.

is what Tom described the best/easiest way to do this?

I think OpenWebAuth is the same thing I’m doing, thank you for the pointer. I’ll see what I can learn there.

We do also OAuth with the PDS to write data, but that is an extra step that we go through in most cases, though it needs to be more obvious in the new user case.

With your feedback, I updated the doc to, hopefully, be more clear.

I’d tread carefully until someone else gives it a thumbs up. Look up service auth and see if that alone gets what you need.

1 Like

Ah, yeah, I see how that’s confusing.

So normally, if I wanted to get logged into another ATProto app, for example, I would have to land on an OAuth login page to approve of a list of scopes before it’s possible for me to get an ATProto session on that other app.

In this case, since OpenMeet has it’s own API and is doing it’s own authorization, and isn’t requiring any scopes, it’s able to take proof of my identity and instantly setup a session from that alone.

I could see that practice being questionable. :thinking:

Because the OAuth check that happens when logging into your PDs is there for a reason.

I guess it’s kind of like, ideally, we’d want to be able to, when logging into Roomy, redirect to the user’s PDS in a way that says, “I want to get a session that is valid for multiple OAuth clients, Roomy, and OpenMeet”. Then if that’s approved, Roomy could send you to OpenMeet in some way such that you can use the current session on Roomy to obtain a session in OpenMeet, having already been given the OAuth prompt that authorizes you to get an OpenMeet session.

That’s not like anything I’ve heard of before.

1 Like