usebruno / bruno

Opensource IDE For Exploring and Testing Api's (lightweight alternative to postman/insomnia)
https://www.usebruno.com/
MIT License
27.22k stars 1.24k forks source link

Oauth2: Supported at the request level, but not at the collection level #1704

Open clocklear opened 8 months ago

clocklear commented 8 months ago

When configuring OAuth 2.0, it would be helpful to configure once per collection instead of requiring configuration on individual requests:

Auth configuration at the request level: image

Auth configuration at the collection level: image

(This is for Bruno v1.10.0)

jackj93 commented 8 months ago

For people that found this page via searching the issue, this is now possible with a little workaround in v1.11.0. Set the collection level auth as OAuth 2.0 (like Insomnia/Postman) Navigate to the "Script" pane of the collection and set the access token as a variable in the "Post response" section

  var token = res.body.access_token;
  if (token) {
    bru.setVar('oauth2_token', token)
  }

Then navigate to the "Headers" pane still in the collection settings and set the "Authorization" header as needed. i.e. image

Once you do this, you only need to "Get the token" once and then all requests in the collection will use this access token.

Full collection.bru for anything that just wants to copy that.

headers {
  Authorization: Bearer {{oauth2_token}}
}

auth {
  mode: oauth2
}

auth:oauth2 {
  grant_type: authorization_code
  callback_url: {{RedirectUrl}}
  authorization_url: {{AuthorizationUrl}}
  access_token_url: {{AccessTokenUrl}}
  client_id: {{ClientId}}
  client_secret: 
  scope: {{GatewayScope}}
  pkce: false
}

script:post-response {
  var token = res.body.access_token;
  if (token) {
    bru.setVar('oauth2_token', token)
  }
}

DO NOT change the request level settings (auth or script), leave the request level Auth settings on "No Auth"

After setting this up, you would just need to navigate to the "Auth" pane in collection settings, press "Get New Token" and you should be good to go to call the protected endpoints in the collection.

dougbreaux commented 7 months ago

@jackj93 this isn't working for me either (v 1.12.1 now). Var isn't being set by the script either when hitting "Get Access Token" button on the Collection Authorization or in making a call that uses inherit authorization.

EirikHaughom commented 7 months ago

@jackj93 did you have to install an external module for this to work? When putting the code you mention in the pre request script location on the collection, I just get this error:

Error invoking remote method 'send-http-request': ReferenceError: res is not defined

Any idea?

jackj93 commented 7 months ago

@jackj93 this isn't working for me either (v 1.12.1 now). Var isn't being set by the script either when hitting "Get Access Token" button on the Collection Authorization or in making a call that uses inherit authorization.

In the request themselves you have to set the "No Auth"

@jackj93 did you have to install an external module for this to work? When putting the code you mention in the pre request script location on the collection, I just get this error:

Error invoking remote method 'send-http-request': ReferenceError: res is not defined

Any idea?

All the configuration I listed go in the configuration for the collection. do NOT put anything in the request settings The script is meant to be put in the "Post Response", sorry if that part wasn't clear

EirikHaughom commented 7 months ago

Thank you @jackj93, it works now! Retrospectively I should've seen in the code that it was going in the post response part.

clocklear commented 7 months ago

Update: As of v1.11.0 I see that OAuth2 is supported at the collection level, but setting auth to 'inherit' at the request level isn't supported:

image

Using @jackj93 's scripting provides a workaround: we can save the collection token as a variable and use that in requests in the appropriate place (for me, I set an Authorization header with the value Bearer {{oauth2_token}}). Is inherit going to be supported natively in the future without scripting hacks?

baldursson commented 5 months ago

In addition to the Post Request script by @jackj93, I'm using this Pre Request script on Collection level, then you don't need to add the header manually on each request:

var token = bru.getVar('oauth2_token')
if (token) {
  req.setHeader('Authorization', `Bearer ${token}`)
}
devsales commented 4 months ago

Is there a way to reuse a token across multiple collections?

I have many collections but they use the same OAuth provider. So now I have to get a token for each collection individually, instead of doing it once and reusing it in all collections.

I guess a workaround would be to have only one collection per OAuth provider and group the requests in folders and subfolders.

danielloader commented 3 months ago

Workarounds aside (which work, thanks for that) is there intention to work on this formally? Debating the pros and cons of waiting on this to be solved upstream and permit inheritance on the collection requests when using Oauth2 instead of injecting a pre-script one every single request.

Thanks

hardiksethi22 commented 3 months ago

I am taking approach the insomnia way for this

created a request responsible for just fetching the token and storing the variable in post response

Screenshot 2024-08-01 at 00 12 19

then using the variable in header.

Screenshot 2024-08-01 at 00 12 39

Only friction with this approach is we have to fetch token manually once before using any of the api in our collection.

insomnia saves a little time in this by allowing us to directly use the attribute from some other response. Also it can auto refresh, hoping to see similar feature in bruno as well :)

image
johnnyggalt commented 3 months ago

Just a suggestion: until such time that the header can be added automatically for OAuth, perhaps this message could point people to this thread:

image

It took me 20 mins to track this down.

espada-edalex commented 2 months ago

After applying @jackj93's workaround (thanks for that) requests are fine, however, when attempting to get a new token I'm getting invalid_client errors. I tracked this down to the Authorization header being added, and disabling it allows a new token to be generated. Has anyone had this issue, and possibly got a better way to handle it than manually toggling the header value?

jplutarch commented 1 week ago

The work arounds suggested still seem to require manually clicking the "Get Access Token" on the collection every time the token expires. This isn't great for api's that use short lived tokens. Luckily the client credential flow can be automated fairly simply by adding the following Pre Request script on the collection:

const axios = require('axios');

let token = bru.getEnvVar('access_token_set_by_collection_script');
let tokenExpiration = bru.getEnvVar('access_token_expiration');
let currentTime = (new Date()).getTime();

if (!token || !tokenExpiration || tokenExpiration < currentTime) {
  try {
    console.log("requesting new token");
    const tokenData = (await axios.post(
      bru.getEnvVar('AccessTokenUrl'),
      {
        client_id: bru.getEnvVar('ClientId'),
        client_secret: bru.getEnvVar('ClientSecret'),
        grant_type: 'client_credentials'
      },
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded"
        }
      }
    )).data;
    let now = new Date();
    let expiration = (now.setSeconds(now.getSeconds() + tokenData.expires_in - 60)); //Add a minute buffer to expiration time.
    bru.setEnvVar('access_token_set_by_collection_script', tokenData.access_token);
    bru.setEnvVar('access_token_expiration', expiration);
  } catch (error) {
    console.log(error);
  }
} else {
  console.log("using existing token");
}

req.setHeader('Authorization', `Bearer ${bru.getEnvVar('access_token_set_by_collection_script')}`)

This stores the access token and token expiration time in environment variables and checks each request to see if a new token needs to be retrieved. It would be really helpful if the bruno Oauth2 documentation for client credentials provided an example like this to simplify the authentication process.

clocklear commented 1 week ago

Since the majority of use-cases use the same scripts at this point, is there a reason why this functionality isn't baked into the client?

thusila-bandara commented 2 days ago

I tried it in another way.

Place below script in post response under collection setting

if(req.getAuthMode() == 'oauth2' && res.body.access_token) { bru.setVar('access_token_set_by_collection_script', res.body.access_token); }

Then go to your request and change the Auth setting to Bearer Token and place below code

{{access_token_set_by_collection_script}}