Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth2 Authentication not working #3410

Open
PIPOGit opened this issue Jan 19, 2025 · 7 comments
Open

OAuth2 Authentication not working #3410

PIPOGit opened this issue Jan 19, 2025 · 7 comments
Labels
wontfix This will not be worked on

Comments

@PIPOGit
Copy link

PIPOGit commented Jan 19, 2025

Hello everyone.

Maybe I'm wrong (or not using correctly the API but) I think there is something that does NOT work with the Authentication when dealing protected resources.

Describe the bug

Using Vanilla Javascript (crypto.subtle, +jQuery...), I'm trying to figure out which should be the steps to gain access to an OAuth2 protected resource; let's say, You want to access getAccountInfo endPoint. As stated in that page, "Requests usually require admin authentication and are made directly to the PDS instance".

So, I try to use the OAuth2 Bluesky API to authenticate and prove how to use OAuth(2) API.

To Reproduce

[TL;DR] Steps to reproduce the behavior:

  1. With my handler: [madrilenyer.bsky.social], retrieve my did, calling: [GET] https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=madrilenyer.bsky.social
    Received: [did:plc:tjc27aje4uwxtw5ab6wwm4km]
  2. Use the did:plc API to retrieve the didDocument.
    Call to the API: [GET] https://plc.directory/did:plc:tjc27aje4uwxtw5ab6wwm4km
    Detected: PDS Server URL: https://velvetfoot.us-east.host.bsky.network
  3. Request PDS Server Metadata
    Call to the: [GET] https://velvetfoot.us-east.host.bsky.network/.well-known/oauth-protected-resource
    Detected: Authorization Server URL: https://bsky.social
  4. Requesting the Authorization Server Discovery Information:
    Call to the: [GET] https://bsky.social/.well-known/oauth-authorization-server
    Returned a lot of metadata information. (You can guess!)
  5. In order to retrieve the user token, first we must call to the PAR endPoint.
    • Generated a "state" value.
    • Generated a "code_verifier" value.
    • Generated a "code_challenge" value from the code_verifier.
    • Prepared the body of the POST call: response_type=code&code_challenge_method=S256&scope=atproto+transition%3Ageneric&client_id=https%3A%2F%2Fmadrilenyer.neocities.org%2Fbsky%2Foauth%2Fclient-metadata.json&redirect_uri=https%3A%2F%2Fmadrilenyer.neocities.org%2Fbsky%2Foauth%2Fcallback%2F&code_challenge=lTN0sTfvJ-T7Gu3Qj-DUTePbJuW5YYs9ptEfpACx7Aw&state=4e47aaac8cbd35ed1a2afff53ce6f4511898d7c2ef0e47b37d77110f&login_hint=madrilenyer.bsky.social
    • Prepared the proper header.
    • Perform the POST to: https://bsky.social/oauth/par
      Received 201 (Created) and even a "[DPoP-Nonce]" header: [vLMqCdbxb7tHxsG9AgnnhtMwJmuJjiMKzwYZsJLp7-M]
      Received this body: [{request_uri: 'urn:ietf:params:oauth:request_uri:req-f4747cc3409f394a6ae7698669a6c0a4', expires_in: 299}]
  6. Using that value, prepare an URL to redirect the browser to the Bluesky Authentication Page: https://bsky.social/oauth/authorize?client_id=https%3A%2F%2Fmadrilenyer.neocities.org%2Fbsky%2Foauth%2Fclient-metadata.json&request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Areq-f4747cc3409f394a6ae7698669a6c0a4
    The "client_id": https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json
  7. The Bluesky Authentication Page redirects to a "Neocities Callback Page": https://madrilenyer.neocities.org/bsky/oauth/callback/?iss=https%3A%2F%2Fbsky.social&state=4e47aaac8cbd35ed1a2afff53ce6f4511898d7c2ef0e47b37d77110f&code=cod-b17f75f356b83f35e99c4d7664ed30442a9c79c5c37ecf88261d77db799d0c0f
    This callback page, just takes the URL (it's a demo/test environment, U know ;^)) and redirects to the same page but in localhost.
    Once in localhost, the same URL is analized to retrieve the "code": [cod-b17f75f356b83f35e99c4d7664ed30442a9c79c5c37ecf88261d77db799d0c0f]
  8. To finally request the access token, we need a body and a DPoP.
    The body is: {grant_type: 'authorization_code', code: 'cod-b17f75f356b83f35e99c4d7664ed30442a9c79c5c37ecf88261d77db799d0c0f', code_verifier: '537c23873b7bcdc4269afa8360604d6d46c18b4e59bff1917fe4121b', client_id: 'https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json', redirect_uri: 'https://madrilenyer.neocities.org/bsky/oauth/callback/'}
    The DPoP is: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiNE9FNzZiY2xwNHVQdmpVelM0UnhNU1d0SWVydU1ic3YyaXRPY1hfb1o3YyIsInkiOiItVW5feGlCcU9fMjYxdDZseXUxQjVjeE8yS1lESmlEdWNnOVlUMnFoQTU0In19.eyJpc3MiOiJodHRwczovL21hZHJpbGVueWVyLm5lb2NpdGllcy5vcmcvYnNreS9vYXV0aC9jbGllbnQtbWV0YWRhdGEuanNvbiIsImp0aSI6ImQwZjFhNDBjLTJiNDAtNDgxMC04YmEyLWIxMGUwYjkzOTliZiIsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2Jza3kuc29jaWFsL29hdXRoL3Rva2VuIiwiaWF0IjoxNzM3MzA1OTExLCJub25jZSI6InZMTXFDZGJ4Yjd0SHhzRzlBZ25uaHRNd0ptdUpqaU1LendZWnNKTHA3LU0ifQ.pOEoi5SejCeQPa7TODGdQVUrN0ytE_UQPvsZuP9GMlptKC-TybkN_bGqWb8IRx6fP5OGwBr7n8L6Hnj-DxjY4A
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "4OE76bclp4uPvjUzS4RxMSWtIeruMbsv2itOcX_oZ7c",
    "y": "-Un_xiBqO_261t6lyu1B5cxO2KYDJiDucg9YT2qhA54"
  }
}.{
  "iss": "https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json",
  "jti": "d0f1a40c-2b40-4810-8ba2-b10e0b9399bf",
  "htm": "POST",
  "htu": "https://bsky.social/oauth/token",
  "iat": 1737305911,
  "nonce": "vLMqCdbxb7tHxsG9AgnnhtMwJmuJjiMKzwYZsJLp7-M"
}.[signature]

The request body is: grant_type=authorization_code&code=cod-b17f75f356b83f35e99c4d7664ed30442a9c79c5c37ecf88261d77db799d0c0f&code_verifier=537c23873b7bcdc4269afa8360604d6d46c18b4e59bff1917fe4121b&client_id=https%3A%2F%2Fmadrilenyer.neocities.org%2Fbsky%2Foauth%2Fclient-metadata.json&redirect_uri=https%3A%2F%2Fmadrilenyer.neocities.org%2Fbsky%2Foauth%2Fcallback%2F
9. Received an access token from the Authorization Server
Access Token: eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJhdWQiOiJkaWQ6d2ViOnZlbHZldGZvb3QudXMtZWFzdC5ob3N0LmJza3kubmV0d29yayIsImlhdCI6MTczNzMwNTkxMSwiZXhwIjoxNzM3MzA5NTExLCJzdWIiOiJkaWQ6cGxjOnRqYzI3YWplNHV3eHR3NWFiNnd3bTRrbSIsImp0aSI6InRvay1lMjkwMmIzNjVhMWM1YWUwNjZiYzdkODM4OWI1MWJiYiIsImNuZiI6eyJqa3QiOiJyNUg2dmd2YmZ2QW50UUFIWnNWOFY4aEV3dXVXS3VZRURaUFRWMzlRNjQwIn0sImNsaWVudF9pZCI6Imh0dHBzOi8vbWFkcmlsZW55ZXIubmVvY2l0aWVzLm9yZy9ic2t5L29hdXRoL2NsaWVudC1tZXRhZGF0YS5qc29uIiwic2NvcGUiOiJhdHByb3RvIHRyYW5zaXRpb246Z2VuZXJpYyIsImlzcyI6Imh0dHBzOi8vYnNreS5zb2NpYWwifQ.OmMWA1QCkWSuob4NAu2ZdmbN4_oEERUYpn7eajWWQRPCT6b1SUnlu4Ge9zoDht_ifdxC_RFncpxA6bQRP9NlJQ

{
    "typ": "at+jwt",
    "alg": "ES256K"
}.{
    "aud": "did:web:velvetfoot.us-east.host.bsky.network",
    "iat": 1737305911,
    "exp": 1737309511,
    "sub": "did:plc:tjc27aje4uwxtw5ab6wwm4km",
    "jti": "tok-e2902b365a1c5ae066bc7d8389b51bbb",
    "cnf": {
        "jkt": "r5H6vgvbfvAntQAHZsV8V8hEwuuWKuYEDZPTV39Q640"
    },
    "client_id": "https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json",
    "scope": "atproto transition:generic",
    "iss": "https://bsky.social"
}.OmMWA1QCkWSuob4NAu2ZdmbN4/oEERUYpn7eajWWQRPCT6b1SUnlu4Ge9zoDht/ifdxC/RFncpxA6bQRP9NlJQ
  1. We've got the access token!
    So, once we've got it, let's try if... Make a simple (POST) call to a common endPoint: introspection: https://bsky.social/oauth/introspect
    Prepare a DPoP-Proof with these values:
  • atHash: MfsZ7uGva_1DpqqHJYb6iP57navhW46f95d9s_Tv-UE
  • jwk: {crv: 'P-256', kty: 'EC', x: '4OE76bclp4uPvjUzS4RxMSWtIeruMbsv2itOcX_oZ7c', y: '-Un_xiBqO_261t6lyu1B5cxO2KYDJiDucg9YT2qhA54'}
  • key: {publicKey: CryptoKey, privateKey: CryptoKey}
  • resourceUrl: https://bsky.social/oauth/introspect
  • clientId: https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json
  • nonce: vLMqCdbxb7tHxsG9AgnnhtMwJmuJjiMKzwYZsJLp7-M
  • method: GET
    Prepared a POST call with:
  • headers: [Authorization: DPoP [access_token]] and [DPoP: [dpop-proof]]
  • body: token=eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJhdWQiOiJkaWQ6d2ViOnZlbHZldGZvb3QudXMtZWFzdC5ob3N0LmJza3kubmV0d29yayIsImlhdCI6MTczNzMwNTkxMSwiZXhwIjoxNzM3MzA5NTExLCJzdWIiOiJkaWQ6cGxjOnRqYzI3YWplNHV3eHR3NWFiNnd3bTRrbSIsImp0aSI6InRvay1lMjkwMmIzNjVhMWM1YWUwNjZiYzdkODM4OWI1MWJiYiIsImNuZiI6eyJqa3QiOiJyNUg2dmd2YmZ2QW50UUFIWnNWOFY4aEV3dXVXS3VZRURaUFRWMzlRNjQwIn0sImNsaWVudF9pZCI6Imh0dHBzOi8vbWFkcmlsZW55ZXIubmVvY2l0aWVzLm9yZy9ic2t5L29hdXRoL2NsaWVudC1tZXRhZGF0YS5qc29uIiwic2NvcGUiOiJhdHByb3RvIHRyYW5zaXRpb246Z2VuZXJpYyIsImlzcyI6Imh0dHBzOi8vYnNreS5zb2NpYWwifQ.OmMWA1QCkWSuob4NAu2ZdmbN4_oEERUYpn7eajWWQRPCT6b1SUnlu4Ge9zoDht_ifdxC_RFncpxA6bQRP9NlJQ
    Received 400 (Bad Request): [{error: 'invalid_request', error_description: 'Validation of "" body parameter failed: Invalid input'}]
  1. Tried a "common accesible" URL, like: "getFollowers": https://public.api.bsky.app/xrpc/app.bsky.graph.getFollowers?actor=did:plc:tjc27aje4uwxtw5ab6wwm4km&limit=5
    Received a list o 5 followers. It works.
  2. Finally, tried our main goal: "getAccountInfo": https://velvetfoot.us-east.host.bsky.network/xrpc/com.atproto.admin.getAccountInfo?did=did:plc:tjc27aje4uwxtw5ab6wwm4km
    As before, prepare a DPoP Proof for this call:
+ accessToken: {
    "typ": "at+jwt",
    "alg": "ES256K"
}.{
    "aud": "did:web:velvetfoot.us-east.host.bsky.network",
    "iat": 1737305911,
    "exp": 1737309511,
    "sub": "did:plc:tjc27aje4uwxtw5ab6wwm4km",
    "jti": "tok-e2902b365a1c5ae066bc7d8389b51bbb",
    "cnf": {
        "jkt": "r5H6vgvbfvAntQAHZsV8V8hEwuuWKuYEDZPTV39Q640"
    },
    "client_id": "https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json",
    "scope": "atproto transition:generic",
    "iss": "https://bsky.social"
}.OmMWA1QCkWSuob4NAu2ZdmbN4/oEERUYpn7eajWWQRPCT6b1SUnlu4Ge9zoDht/ifdxC/RFncpxA6bQRP9NlJQ
+ dpopProof: {
    "typ": "dpop+jwt",
    "alg": "ES256",
    "jwk": {
        "crv": "P-256",
        "kty": "EC",
        "x": "4OE76bclp4uPvjUzS4RxMSWtIeruMbsv2itOcX_oZ7c",
        "y": "-Un_xiBqO_261t6lyu1B5cxO2KYDJiDucg9YT2qhA54"
    }
}.{
    "iss": "https://madrilenyer.neocities.org/bsky/oauth/client-metadata.json",
    "jti": "2d190d32-a596-42d5-869d-3b0ce63bffcd",
    "htm": "GET",
    "htu": "https://velvetfoot.us-east.host.bsky.network/xrpc/com.atproto.admin.getAccountInfo?did=did:plc:tjc27aje4uwxtw5ab6wwm4km",
    "iat": 1737305911,
    "ath": "MfsZ7uGva_1DpqqHJYb6iP57navhW46f95d9s_Tv-UE",
    "nonce": "vLMqCdbxb7tHxsG9AgnnhtMwJmuJjiMKzwYZsJLp7-M"
}.yqGj2StveFZE93TUAoRV6byJe1g2fIYiSiWnwVTclm4/5GGJZw2zkwkvJR92vVZI30284TQjXjpCbuLtneetxg

Received 401 (Unauthorized): [{error: 'AuthenticationRequired', message: 'Authentication Required'}]

Expected behavior

A couple of things:

  1. I guess, the "Discovery" introspection endpoint should NOT need authentication.
  2. There is a lack of information about "how to use" the (many") calls to perform, and the data to manage among them, to correctly execute an OAuth2 protected endpoint.

Details

  • Operating system: Windows 11
  • Browser: Chrome, last version
  • Node version: Vanilla Javascript

Additional context

As I stated first, maybe I'm wrong using the API, or something is missing somewhere.
As I said, I'm trying to figuer out how to access OAuth2 protected resources with vanilla Javascript; for eduational purposes.

If someone can hekp me with this "last step"...

Many thanks in advance.

@PIPOGit PIPOGit added the bug Something isn't working label Jan 19, 2025
@matthieusieben
Copy link
Contributor

The reason you are getting this is that the getAccountInfo endpoint does not allow Oauth credentials to authenticate users.

The com.atproto lexicons (currently?) don't have a concepts of "roles" on the accounts. You can't create "admin" users and "regular" users. The way user management is done is 1) you get "admin" credentials when you setup your own PDS, which 2) you can use (using Basic auth) to make requests to the "user management endpoints".

OAuth credentials are only allow to perform actions as "regular users".

@matthieusieben
Copy link
Contributor

matthieusieben commented Jan 20, 2025

Note that we would be open to discussing the need for being able to add user management (and roles). Feel free to open a Github discussion.

Until we reach a consensus on the subject, I'm labelling this issue as "wontfix".

@matthieusieben matthieusieben added wontfix This will not be worked on and removed bug Something isn't working labels Jan 20, 2025
@matthieusieben
Copy link
Contributor

matthieusieben commented Jan 20, 2025

That being said, if all you need is to getAccountInfo for the currently logged in ("regular") used, we are open to accept a pull request that adds authVerifier.accessFull as auth method.

@PIPOGit
Copy link
Author

PIPOGit commented Jan 20, 2025

Well... I see.

So, what I think is that there should be a catalog of API's/EndPoints, depending on the way You can use them.

Let's say: PUBLIC access (no authentication needed), PROTECTED (with Basic, OAuth -DPoP-) and RESTRICTED (allowed ONLY for some "roles").

By the moment, there is no chance to guess if a given EndPoint (from outside the development team) to determine if an endpoint needs authentication/"roles" and which one.

Thanks for the response!!!

@matthieusieben
Copy link
Contributor

That is a good & valid point. I'll bring this up to the team.

@PIPOGit
Copy link
Author

PIPOGit commented Jan 20, 2025

That is a good & valid point. I'll bring this up to the team.

For instance...

I'm trying to call: getProfile, with my [userDid==did:plc:tjc27aje4uwxtw5ab6wwm4km].

I'm using these headers:

{
  "Authorization": "DPoP eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJhdWQiOiJkaWQ6d2ViOnZlbHZldGZvb3QudXMtZWFzdC5ob3N0LmJza3kubmV0d29yayIsImlhdCI6MTczNzM3MTMxOCwiZXhwIjoxNzM3Mzc0OTE4LCJzdWIiOiJkaWQ6cGxjOnRqYzI3YWplNHV3eHR3NWFiNnd3bTRrbSIsImp0aSI6InRvay03ZDZkMDJlMzY4ODY2ZjVhNmJlMWUwNDc4MGY4YjQzYiIsImNuZiI6eyJqa3QiOiJ5NV80ODhXNElPY001YTRudndkTWJBLXl2RFkzSm5MeWpsMTBYQXNuVVFJIn0sImNsaWVudF9pZCI6Imh0dHBzOi8vbWFkcmlsZW55ZXIubmVvY2l0aWVzLm9yZy9ic2t5L29hdXRoL2NsaWVudC1tZXRhZGF0YS5qc29uIiwic2NvcGUiOiJhdHByb3RvIHRyYW5zaXRpb246Z2VuZXJpYyIsImlzcyI6Imh0dHBzOi8vYnNreS5zb2NpYWwifQ.d6cFMecRx-mkuIxc2DcnVUztcP7iR_mg1aNkJT2axcR0vIV1RrX3nn5HMD2m2UtnoDtzfSdcOs8bO8n0J7jNvg",
  "DPoP": "eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiZTg2LWVRTVNFQjM4UFY1RTE5ejJIX0Jmd0RUQkR1T2Y0SElsNDJzUlNvYyIsInkiOiJuVVA1bVRlYjJ6UkxxdWI1TVZYZFhCUWRndUM5LWNvYTdzbmZJUE1tLUdJIn19.eyJpc3MiOiJodHRwczovL21hZHJpbGVueWVyLm5lb2NpdGllcy5vcmcvYnNreS9vYXV0aC9jbGllbnQtbWV0YWRhdGEuanNvbiIsImp0aSI6IjExYjQ3NGM4LWYzN2YtNGJlYS04ZjI5LTFkMDdhMTg0NGExZSIsImh0bSI6IkdFVCIsImh0dSI6Imh0dHBzOi8vdmVsdmV0Zm9vdC51cy1lYXN0Lmhvc3QuYnNreS5uZXR3b3JrL3hycGMvYXBwLmJza3kuYWN0b3IuZ2V0UHJvZmlsZT9hY3Rvcj1kaWQ6cGxjOnRqYzI3YWplNHV3eHR3NWFiNnd3bTRrbSIsImlhdCI6MTczNzM3MTMyNCwiYXRoIjoiVGJFTTZvS3NiNkk4S19tRFY4TjBmXzZoMU02WjNMRklMdUxLN3h6YnhNMCIsIm5vbmNlIjoiVjBsbHU2SGo1bENBQWFjbFB1VXAxd21NQU9OM0N6OE1ILVRpeHNZUXZEbyJ9.Mw3qTcSmfxMW97i0wHJj3Ro_6rGSZxcb9eQ8TFcR_DVVTe3lrXsg_HSXCcWzzETVmuWKx-QjBzDCWUt1s2j_1A",
  "Accept": "application/json"
}

... and this is the response:

{
  "error": "use_dpop_nonce",
  "message": "Authorization server requires nonce in DPoP proof"
}

What's wrong? Is it there is something missing?

If you look at the reference page, you can see: "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth". I guess that a call to this endpoint is "open" but, if you pass a token, you'll get "extra" info.

Let's check if that call is really "open"... If you open the link above in a broser (tab), here is the response:

{
    "error": "AuthMissing",
    "message": "Authentication Required"
}

I miss something... ;^)

@PIPOGit
Copy link
Author

PIPOGit commented Jan 20, 2025

Well... I found the "mistake".

The link below, getProfile was for: https://velvetfoot.us-east.host.bsky.network.

Using: https://public.api.bsky.app instead, (public) getProfile works fine. Both mechanisms, with/without authentication worked. But with no difference.

Sorry! 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

2 participants