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

API access with client credentials (core functionality) #16817

Merged
merged 14 commits into from
Jul 29, 2024

Conversation

kjac
Copy link
Contributor

@kjac kjac commented Jul 25, 2024

Prerequisites

  • I have added steps to test this contribution in the description below

Description

This PR introduces API access via client credentials (client ID + secret) to both the Management and the Delivery API. Client credentials is a necessary access mechanism for machine-to-machine integrations with these APIs.

The feature works by impersonating a concrete User (Management API) or Member (Delivery API). Thus we can reuse the entire permissions setup for both APIs.

Please note that this PR actively ensures a prefix for all created client IDs. This is to avoid collisions with the core client IDs:

  • Management API client IDs are prefixed umbraco-back-office-.
  • Delivery API client IDs are prefixed umbraco-member-.

Also note that this PR does not include any UI support for managing client secrets. Only the core functionality (including Management APIs for client secrets) is included here.

The Management API

With this PR it becomes possible to define pairs of client IDs and secrets for a specific User. In other words, one User can be associated with multiple client credentials. There are multiple applications for this, including rotation of client credentials.

Managing access to the Management API is quite critical, so we want to ensure that it's possible to distinguish between regular Users and integration Users in the UI. To that end, a new concept of "User type" is introduced:

  • Default: A regular back-office User.
  • Api: A back-office User meant to be used with client credentials for API access.

Client credentials can only be defined for Api Users. Likewise, Api Users are not allowed to define passwords, making it impossible to sign into the back-office with an Api User.

We only ever store the client IDs in the Umbraco database (the umbracoUser2ClientId table). We use them figure out which User to impersonate for a given client ID, and to avoid collisions between client IDs. The client secrets however are stored and handled exclusively by OpenIddict. This means that:

  1. Once a set of client credentials have been created, we cannot retrieve the client secret again. Thus, the UI must encourage admins to copy the client secret before saving a set of client credentials.
  2. A client secret can only be changed by deleting and re-creating the set of client credentials.

At this point there are no complexity requirements for client secrets. We might consider enforcing a certain level of client secret complexity in the future.

The Delivery API

Member authorization is defined by configuration (see the docs for details).

When introducing Member authorization (with Authorization Code Flow + PKCE) in the Delivery API, we prepared the MemberAuthorization configuration for an eventual future with more authorization flows. That future is now 😄 and the configuration has been extended with a ClientCredentialsFlow option:

"MemberAuthorization": {
  "ClientCredentialsFlow": {
	"Enabled": true,
	"AssociatedMembers": [
	  {
		"ClientId": "client-2",
		"ClientSecret": "really-really-secret",
		"UserName": "test@test"
	  }
	]
  }
}
  • As with all things Delivery API, the client credentials auth flow must be explicitly enabled ("Enabled": true).
  • The impersonation is defined in AssociatedMembers, which is an array that maps client credentials and Members. This makes it possible to have multiple client credentials mapped to different (or the same) Member access setups.

Testing this PR

Management API, Client Credentials Flow

Using Swagger:

  1. Create an Api User (use "type": "Api" in the payload).
  2. Add client credentials to the newly created User.
  3. Verify that it is not possible to add client credentials to any other (regular) user.
  4. Verify that it is not possible to add duplicate client IDs.

Download this Node app and tweak the .env variables to match your setup:

  • UMBRACO_CLIENT_ID and UMBRACO_CLIENT_SECRET must match the your client credentials (client ID and secret, respectively).
  • UMBRACO_DOCUMENT_ID must be an ID (key) of an existing document in your site.

Run the Node app from a terminal with npm run start. The app should print out the resulting JSON from the /umbraco/management/api/v1/document/{id} Management API endpoint:

image

Using Swagger, delete the client credentials from the Api User. Re-run the Node app and verify that it is no longer allowed to access the Management API.

Management API, Authorization Code Flow + PKCE

Verify that you can still log into the back-office 😆

Delivery Api, Client Credentials Flow

In Umbraco:

  1. Create a document type with alias article (it doesn't need to have any properties).
  2. Publish some documents of this type.
  3. Restrict public access to one or more (but not all) of these documents to a specific Member or Member Group.
    • If you use a Member Group for your public access configuration, you'll still need a Member to be part of this group in order to perform impersonation later.

By configuration:

  1. Enable the Delivery API if you haven't already (remember to rebuild the Delivery API index if applicable).
  2. Configure ClientCredentialsFlow as described above.

Download this Node app and tweak the .env variables, so UMBRACO_CLIENT_ID and UMBRACO_CLIENT_SECRET match the your client credentials (client ID and secret, respectively).

Run the Node app from a terminal with npm run start. The app should print out all the article documents available to the configured client credentials.

For good measure, verify that /umbraco/delivery/api/v2/content/?fetch=children:/&filter=contentType:article only outputs the publicly available article documents when requested in a browser (i.e. anonymous access).

Delivery API, Authorization Code Flow + PKCE

These tests are quite cumbersome. It might be easier to get in touch with @kjac who's got a test setup ready to go.

Create an application as described in this docs artice and verify that Authorization Code Flow + PKCE is not broken by this PR.

Also, ensure that refresh tokens still work (it might be simplest to do with Postman, once the application above has obtained a valid token).

Upgrading Umbraco

The new "User type" means that the User class is expanded with a new Type property. Historically this has sometimes been a challenge when performing upgrades.

Test that it's possible to perform an upgrade to this PR:

  1. Both unattended and attended.
  2. Both using SQLite and SQL Server.

Breaking changes

The changes in this PR are binary breaking, due to the new methods in IBackOfficeApplicationManager, IUserService and IUserRepository. However, the PR targets a new major (V15.0), so binary breakage is probably OK 😄

Copy link
Member

@elit0451 elit0451 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests out great 💪 🙌

@elit0451 elit0451 merged commit 68db079 into v15/dev Jul 29, 2024
15 of 16 checks passed
@elit0451 elit0451 deleted the v15/feature/client-credentials branch July 29, 2024 12:34
@kjac kjac mentioned this pull request Aug 14, 2024
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants