muni-town / weird

Weird web pages
https://weird.one
Other
49 stars 11 forks source link

Login with Bluesky (atproto) #206

Open erlend-sh opened 1 month ago

erlend-sh commented 1 month ago

Essential reading: https://docs.bsky.app/blog/oauth-atproto https://atproto.com/specs/oauth

The immediate goal is support for Bluesky-login in Rauthy. In other words we wanna include ‘Login with Bluesky’ as an option here: https://a.weird.one/login

The closest reference for it already in Rauthy is GitHub-login, which also doesn’t use OIDC.

@anderspitman said:

I think these are the salient points for Rauthy:

  • Ephemeral clients should be the same (client metadata json file)
  • The reason they're not doing OIDC is because of DIDs. You need to check a two way binding at the end of the flow to make sure the DID points at the auth server, and vice versa.
  • PAR is not currently required (at least for bsky.social as AS). They mention this somewhere in their docs and I confirmed with testing. However, login_hint doesn't work unless you use PAR

it might be quicker to just implement the client requests from scratch using an oauth2 library. That's what I did for decent-auth and it wasn't bad at all.

We can probably just use this:

Other useful references here:

avdb13 commented 1 month ago

Can I get assigned?

FlorianDevPhynix commented 4 weeks ago

I'm really interested in pushing this for Rauthy.

I was looking into writing something similar as Rauthy just only for Atproto oauth. An integration into Rauthy would save a lot of work, but my usecase will be a little different from yours.

I and probably most people that will use this Atproto oauth integration will need access to the atproto access tokens (and some additional data), to make requests to bluesky (or other pds servers). This will require a little bit more work than just adding it as another authentication provider to Rauthy.

erlend-sh commented 4 weeks ago

Nice!

Well for starters maybe @avdb13 can ask you for a review when he’s done with the essentials of the ‘add authentication provider to Rauthy’ task, then maybe we’ll let you take it from there.

FlorianDevPhynix commented 4 weeks ago

I also have some experience with Atrium Api and I read some of the new oauth code that was just commited. I can definitely help there if needed. The additional work on the Atrium Api crate is blocking the work on this though. I'm going to try and figure out what is required and which changes need to be made to accommodate for the usecase, where an external authentication provider like Rauthy is used with a server that needs to talk to the atproto network.

erlend-sh commented 4 weeks ago

Sounds good. We’re also hanging out on the community Discord in the #rust channel, which for now has led us here:

https://github.com/bluesky-social/atproto/pull/2756

FlorianDevPhynix commented 3 weeks ago

Is bluesky-social/atproto#2756 really a blocker? Do we need multiple redirect uri's for Rauthy?

erlend-sh commented 3 weeks ago

I’m not sure that it’s a blocker, I’m just saying that’s as far as the explorative development has gotten us this far.

avdb13 commented 3 weeks ago

Alright, I made a draft but it's not ready for testing yet. From what I understood, these are exact steps (quoted from the implemementation guide) that should take place when "Login with Bluesky" is chosen:

  1. Ask the user for one of the following pieces of information:
    • handle (@jay.bsky.social)
    • DID (did=did:plc:z72i7hdynmk6r22z27h6tvur)
    • PDS (https://xn--ls8h.test)
  1. Retrieve the PDS URI, depending on the information from the previous step:

    • handle -> DID document: request the domain's TXT DNS record, or make a request to another PDS' /.well-known/atproto-did endpoint. If the DID method is PLC, send a query to a preconfigured PLC directory, if the method is web call the hostname's /.well-known/did.json.
    • DID document -> PDS: under the document's service array, we wanna match the first entry where id ends with #atproto_pds, and type matches AtprotoPersonalDataServer. The first matching entry should be always be used.
  2. Retrieve the PDS' authorization server URI:

    • PDS -> protected resource metadata: fetch the PDS' /.well-known/oauth-protected-resource, the document should contain a field authorization_servers with a single entry which should point to the user's authorization server (PDS or entryway). protected resource metadata -> authorization server metadata: fetch the PDS' /.well-known/oauth-authorization-server, the document's protected_resources should contain the protected resource metadata's URI.
  3. make a Pushed Authorization Request: // TODO

FlorianDevPhynix commented 3 weeks ago

Summary of Authorization Flow

  1. Retrieve the PDS URI, depending on the information from the previous step:
  • handle -> DID document: request the domain's TXT DNS record, or make a request to another PDS' /.well-known/atproto-did endpoint. If the DID method is PLC, send a query to a preconfigured PLC directory, if the method is web call the hostname's /.well-known/did.json.
  • DID document -> PDS: under the document's service array, we wanna match the first entry where id ends with #atproto_pds, and type matches AtprotoPersonalDataServer. The first matching entry should be always be used.
  1. Retrieve the PDS' authorization server URI:
  • PDS -> protected resource metadata: fetch the PDS' /.well-known/oauth-protected-resource, the document should contain a field authorization_servers with a single entry which should point to the user's authorization server (PDS or entryway). protected resource metadata -> authorization server metadata: fetch the PDS' /.well-known/oauth-authorization-server, the document's protected_resources should contain the protected resource metadata's URI.
  1. make a Pushed Authorization Request: // TODO

This should all be taken care of by #atrium/feature/oauth.

Atproto oauth is based on OAuth 2.1 but there are additional settings, so sadly it's not compatible with the auth_providers. Maybe the data model can be adjusted to combine them, but it should be easier to only do this in the UI.

Also additional endpoints are needed for Client Metadate and JWK's. atrium-oauth-wasm endpoints

Maybe we could try and get some inspiration from the FedCM integration. It also needs Client Metadata so we will need a similar endpoint as this

Atproto oauth also needs state to be stored before redirecting to the authentication server, which needs to be available when the callback is called. A cookie like here could be used for that.

  1. Ask the user for one of the following pieces of information:
  • handle (@jay.bsky.social)
  • DID (did=did:plc:z72i7hdynmk6r22z27h6tvur)
  • PDS (https://xn--ls8h.test)
  • AT URI (do we wanna support this?)

I think it would be important to properly verify this user input. I have tested other atproto oauth apps and there was one that failed if the input was not a proper handle or pds Domain.

erlend-sh commented 3 weeks ago

handle (@jay.bsky.social)

That's the only login method we need for a POC. 99.99% of users are only aware of their handle.

FlorianDevPhynix commented 3 weeks ago

Configurations needed:

avdb13 commented 3 weeks ago

This should all be taken care of by #atrium/feature/oauth.

Indeed, the purpose was documenting this MR and figuring out whether something is missing, i.e. application_type in our client metadata should be configurable to qualify as a confidential client.

Atproto oauth is based on OAuth 2.1 but there are additional settings, so sadly it's not compatible with the auth_providers. Maybe the data model can be adjusted to combine them, but it should be easier to only do this in the UI.

That's what I found out as well, I'm just clueless as to how we should make atproto configurable in the UI.

Also additional endpoints are needed for Client Metadate and JWK's. atrium-oauth-wasm endpoints

Great catch, I think atproto should keep a separate JWKS since invalidating a key means invalidating tokens bound to it.

Atproto oauth also needs state to be stored before redirecting to the authentication server, which needs to be available when the callback is called. A cookie like here could be used for that.

Thanks for the clarification, I was having a hard time figuring out whether a cookie is necessary at all since there's no mentioning of it. What shouls be stored in the cookie though? I thought atrium used its server-side state store to handle the callback.

I think it would be important to properly verify this user input. I have tested other atproto oauth apps and there was one that failed if the input was not a proper handle or pds Domain.

I thought the specification had quite strict validation rules here but please let me know if there's edge cases here.

Thanks for your sharing your thoughts.

erlend-sh commented 3 weeks ago

how we should make atproto configurable in the UI.

What exactly needs to be configurable about atproto in the UI? I don’t think we need any configurability for the POC stage.

avdb13 commented 3 weeks ago

how we should make atproto configurable in the UI.

What exactly needs to be configurable about atproto in the UI? I don’t think we need any configurability for the POC stage.

Yes. Just requires a configuration option for now.

FlorianDevPhynix commented 3 weeks ago

Atproto oauth also needs state to be stored before redirecting to the authentication server, which needs to be available when the callback is called. A cookie like here could be used for that.

Thanks for the clarification, I was having a hard time figuring out whether a cookie is necessary at all since there's no mentioning of it. What should be stored in the cookie though? I thought atrium used its server-side state store to handle the callback.

Atrium oauth currently only provides a memory based state store. This is not replicated though, so users could run into issues with high availability setups. atrium-oauth-wasm used Deno's replicated Key-Value-Store to solve this problem. Rauthy uses a cookie for state when authenticating with a provider here. I just thought that we should use the same approach. The most optimal way of storing this state would be a replicated database like Redis (Valkey), but I'm kind of hesitant to add another database with it's required client library to the project. (there are alread like 600 dependencys)

I think it would be important to properly verify this user input. I have tested other atproto oauth apps and there was one that failed if the input was not a proper handle or pds Domain.

I thought the specification had quite strict validation rules here but please let me know if there's edge cases here.

My mistake. I thought atrium oauth does not do any validation, but it does. Just try invalid handles as inputs for atrium-oauth-wasm demo and an error message will be returned.

Atproto oauth is based on OAuth 2.1 but there are additional settings, so sadly it's not compatible with the auth_providers. Maybe the data model can be adjusted to combine them, but it should be easier to only do this in the UI.

That's what I found out as well, I'm just clueless as to how we should make atproto configurable in the UI.

Yeah, I also already spent some time thinking about this. The differences are too great to combine both data models. So the only options I see are Option A: display as an additional provider separate of the other ones Option B: unify them in the UI B would probably the best for users

erlend-sh commented 3 weeks ago

The work-in-progress can now be tracked and further commented on here:

avdb13 commented 3 weeks ago

work-in-progress for atrium has been moved to https://github.com/sugyan/atrium/pull/242, apologies