Allowing partners to authenticate as (some) users to our API

We have a React + Rails API app that uses Auth0 for authentication. In the legacy app this is replacing, we allowed our partners to create applications that could make authenticated requests to our API on behalf of our users. When a user enabled a particular integration, we would send a JSON payload to the partner app containing the user’s email and an API key, and that app would use those credentials to authenticate when making requests to our API. Most of these apps are used to sync data from their system to ours, so it can happen in the background at any time.

What is the closest equivalent to this, taking advantage of Auth0 features? Ideally, our users wouldn’t have to do any extra work beyond clicking the “Enable X Integration” button, and our partners wouldn’t have to create Authorization pages like “OurApp is requesting access to PartnerApp. [Allow] [Cancel]”. It would also be nice if the partner didn’t have to do the OAuth dance when making these API requests, to minimize disruption for them porting their integrations to the new version of our app.

This would be greatly simplified if Auth0 supported “API Keys”, but from other threads on this forum, it seems that feature isn’t available, and isn’t being worked on.

The two best options I can think of are:

  1. When the user enables the integration, our app uses the Auth0 API to generate a new “refresh token”, and sends that refresh token to the partner app. The partner app uses that refresh token with our Auth0-hosted authentication API to exchange it for an access token, then can use that access token to authenticate with our app’s API. This isn’t really what we want, since it means our partners have to now implement the OAuth dance, and I’m not sure it really has any advantages.
  2. Same as above, but instead of the partner app having to perform the token exchange, we allow them to use that refresh token as a valid credential for our API. I’m not sure how this would work, or if the refresh token supplied by Auth0 provides enough information to authenticate a particular user.

Is there another mechanism I’m missing that might be better suited for this scenario?

TIA

Have you considered the client-credentials grant and third-party applications? This would be ideally suitable when customer’s app (a third-party app) will need to call your API directly (not on behalf of a user).

When you sign-up a customer, you can:

Your customer will have to do the client-credentials exchange, but it’s definitely more straightforward than the interactive flows (those involving the user). They will get an access token that will be valid to call your APIs, and the token will carry the identity of the app that requested the token. And the benefit of this versus the API key is that you get to alter the grants in the future without changing your customer’s API key.

Your customers should cache the token obtained from the token endpoint for its lifetime, to avoid overtaxing the authorization server.

Let me know if that makes sense.

Hi @nicolas_sabena, thanks for your response!

Most of that makes sense, but I think I’m a little confused about the term “customer” here as you’re using it.

In our app, we have “accounts” and “users”, where an account has many users, and a user represents (usually) a person logging in to the app. Separately, we have “integration partners” with their own apps, that our users want to be able to sync their data between our apps. I think your use of “customer” here matches my “integration partner”, but I wanted to clarify. We only have a couple “integration partners” right now, and probably no more than a dozen for the foreseeable future. I’m not sure if that changes the preferred approach for this.

In this case, the partner app is a CMS, and when a user makes changes to a “Contact” in the CMS, that app will use our API to inform us of the changes. However, the CMS app should only have authorization to data within the account for the user that enabled that integration. If I’m understanding this correctly, we would create a single “third-party application” for the CMS, and then each time a user enables the integration for their account, we would create a new “client-credentials grant” for each one? Then following the “client-credentials flow”, does the access token we get in the request from the CMS contain enough information for us to authorize it for access to the particular account?

Per your docs for third-party applications, it looks like this would still have to go through the user consent flow? Your comment seems to indicate this is not the case. First-Party and Third-Party Applications

Thanks,
Paul

The plot thickens :smiley:

I didn’t understand, originally, that this “integration” apps would only be accessing the data of users that enabled the integration.

First of all, let me clarify: when I said “customers”, I indeed meant “integration partners”. Sorry I wasn’t clear on that.

I want to go back on the basic concepts of the client credentials flow, and the OAuth2 delegated authorization.

  • In a client credentials grant an application (either first-party or third-party) obtains an access token to make requests to an API. The app must have been previously granted access to the API, and probably granted certain scopes (if you use scopes for finer grained authorization).
    The “subject” of the token is the application itself, so the API will be able to tell that the application is making requests, but there’s no user involved in the process. Think of an app making requests to a backend database: there’s no user involved in the authorization to the database, even though there’s probably a user using the application.

  • In the OAuth2 user flows, on the contrary, there’s a user involved. The user grants an application access to the user’s resources (although the grant might be implicit on first-party app). Now that I’m understanding your use case better, this is the flow that you’ll need to use.

So, how would this look?

  • You would create one third party application per actual application offered by your partner (e.g. “Acme CMS”). Your partner will need to tell you which callback URL they will use (e.g. “https://acme-cms.com/oauth2/callback”). You’ll enable, for this application, the same connections usually used by your users.
  • Your partner’s app will do interactive (browser-based) authorization requests using the /authorize endpoint (yes, they’ll have to do the full OAuth2 dance). They can request access to your API as well as access to the OIDC scope to get the user profile.
  • If the partner app needs offline access to the user’s resources (i.e. it needs to access the API while the user is not present, as in a batch process) it will need to request a refresh token as well (to be able to get refreshed access tokens without user intervention).
  • When a user wants to “enable” the integration with the partner app, you’ll simply redirect the user to the partner app. The app will somehow need to now that the user is arriving hoping to integrate with your api (this can be done through a URL, an option on your partner app, or other mechanism).
  • The partner app will issue an /authorize request with the desired scopes (e.g. read:contacts update:contacts openid profile email). The user will see a consent screen the first time this happens.
  • The partner app can use the request token to make requests to your API. In the token, you’ll see the sub claim identifying the user, and the azp (authorized party) containing the client ID of the partner app. You’ll use that information, along with the scopes in the token, to make authorization decisions (what can the app do). Of course, you’ll perform the regular token validations as well (expiration, signature, issuer and audience).

In this OAuth2 interactive delegated access model (with a user involved) there will be no client-credentials flow. Client-credentials would only be used if the app needs to talk to your API without acting on behalf of a user.

You can also combine the two if it makes sense for your use case: the third-party app could obtain tokens from users for certain things, and tokens on behalf of itself (client-credentials grant) for certain operations.

Does that make sense?

1 Like

Yup, that was extremely helpful, thank you!

Since in our case our partners are “trusted” (we have contracts with them involving real money), I imagine that instead of creating a 3rd-party app for Acme CMS, we could create a trusted “first-party” app. This would allow us to avoid creating a consent screen and use the normal Auth0 flow (which would silently allow access, just like with our main app). Then we provide the client_id, client_secret and refresh token (or maybe only the refresh token?) to Acme CMS, which they exchange for an access token as needed per the normal flow.

If this is feasible, I think we can make it work. If its not, and we have to force them to use OAuth flow and create consent screens, I don’t think that’ll fly (due to the previously mentioned contracts). In that case, we’ll have to implement our own API token basic auth scheme, which I’m not looking forward to since its a “special case” in our backend auth flow, where previously everything was via Auth0 JWTs.

Thanks again for your help!

Since in our case our partners are “trusted” (we have contracts with them involving real money), I imagine that instead of creating a 3rd-party app for Acme CMS, we could create a trusted “first-party” app. This would allow us to avoid creating a consent screen and use the normal Auth0 flow (which would silently allow access, just like with our main app).

That can be done, it’s up to you. I would still recommend setting the app as third-party for better semantics. Users will do an additional consent step, but this in no way affects or changes the integration work needed from your partner. Essentially, they won’t know or notice if there’s a consent screen or not, this is just for your users (see below).

Then we provide the client_id, client_secret and refresh token (or maybe only the refresh token?) to Acme CMS, which they exchange for an access token as needed per the normal flow.

That’s the part that is not quite right in terms of usage of the OAuth2 protocol.
The proper way would be that you’d b providing the client id and secret, and your partner app is responsible for making the OAuth2 token request (asking for an access token, and a refresh token if needed).

Getting a refresh token for them (impersonating their app?) and handing over the refresh token as some kind of API key or token generation key would not be a proper OAuth2 flow and thus is not something we can recommend.

Oh, I thought from your previous reply that our partners would have to build a “request consent” page, but it looks like they just need to have a page that can handle redirects.

So if I’m understanding this correctly, the flow would look something like this:

  1. User in our app goes to the integrations page, and clicks “Enable Acme CMS”.
  2. We send them to acme-cms.com/pauls-app-integration
  3. Acme sends them to pauls-app.auth0.com/authorize with the client id/secret
  4. User sees an Auth0-hosted consent page, “Acme CMS is requesting offline access to Paul’s App”, clicks “allow”.
  5. Auth0 redirects them to the configured callback URL, acme-cms.com/oauth2/callback
  6. Acme stores the refresh token, redirects them back to our app.

I’m not sure how palatable this will be to our users or our partners. Seems like we might have a couple levels of increasing convenience and decreasing OAuth correctness (third-party app, first-party app to skip the consent page, finally having our app host the redirect instead of our partner app).

Thank you again for your help. Mark us down as +1 for wanting some sort of “API Token” feature as has been mentioned in other threads.

Exactly, they just need to redirect to /authorize when they need a token. The authorization server (Auth0) will handle everything that’s necessary:

  • Authentication (if the user is not authenticated yet)
  • MFA (if enabled)
  • Consent (if there was no consent given before)

The flow you mention is correct. The consent page will only be a “problem” for users, but think of it as a reassurance. The same type of consent you would get if, for instance, you are connecting a calendar scheduler helper to your Google calendar.