|
| 1 | +--- |
| 2 | +description: Setup OAuth authorization for Umbraco Management API in local and production environments. |
| 3 | +--- |
| 4 | + |
| 5 | +# Overview |
| 6 | + |
| 7 | +{% hint style="info" %} |
| 8 | + |
| 9 | +This guide is created by a community member and is not managed by Umbraco HQ. Some attributes or features may evolve as the Management API continues to develop. |
| 10 | + |
| 11 | +{% endhint %} |
| 12 | + |
| 13 | +This guide covers how to set up OAuth authorization for the Umbraco Management API for both local development and production environments. Authorization configuration can differ greatly between environments, and understanding these differences is key. |
| 14 | + |
| 15 | +Before proceeding, it is recommended to read the [Management API overview](./README.md). It provides fundamental information about the authorization process and its significance. |
| 16 | + |
| 17 | +This guide will walk through: |
| 18 | + |
| 19 | +1. [Environment Differences and Challenges](#environment-differences-and-challenges) |
| 20 | +2. [Configuring appsettings.json](#configuring-appsettingsjson) |
| 21 | +3. [Setting up Production-Local Authorization](#setting-up-production-local-authorization) |
| 22 | +4. [Creating a Custom Client ID](#creating-a-custom-client-id) |
| 23 | +5. [Minimal API Implementation](#minimal-api-implementation) |
| 24 | +6. [Configuring Authorization in Production](#configuring-authorization-in-production) |
| 25 | +7. [Common Pitfalls and Troubleshooting](#common-pitfalls-and-troubleshooting) |
| 26 | + |
| 27 | +# Environment Differences and Challenges |
| 28 | + |
| 29 | +The Umbraco Management API authorization works seamlessly in non-production environments using tools like Swagger or Postman. However, in production, some key differences and limitations exist: |
| 30 | + |
| 31 | +- **Swagger and Postman Integration**: Only allowed in non-production, facilitating testing. |
| 32 | +- **Client Restrictions**: In production, only the `umbraco-back-office` client is allowed. |
| 33 | +- **OAuth2 Flows:** Requires careful handling to ensure secure setup without Swagger available. |
| 34 | + |
| 35 | +To avoid conflicts and guarantee smooth integration in production, it's crucial to create a custom client and tailor the authorization flow accordingly. |
| 36 | + |
| 37 | +# Configuring appsettings.json |
| 38 | + |
| 39 | +To override the default callback URL for OAuth authorization, update the `appsettings.json` file as follows: (this uses client: `umbraco-back-office`) |
| 40 | + |
| 41 | + |
| 42 | +```json |
| 43 | +"Umbraco": { |
| 44 | + "CMS": { |
| 45 | + "Security": { |
| 46 | + "AuthorizeCallbackPathName": "/callback" |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +This configuration specifies a custom callback path for OAuth. However, it may interfere with the default backoffice callback, affecting accessibility. |
| 53 | + |
| 54 | +# Setting up Production-Local Authorization |
| 55 | + |
| 56 | +In a production environment, Swagger UI is disabled, and only the `umbraco-back-office` client can be used. This requires a more advanced approach. |
| 57 | + |
| 58 | +# Creating a Custom Client ID |
| 59 | + |
| 60 | +To avoid conflicts with the backoffice, a new client should be created. Below are the steps to set up a custom client using a Minimal API: |
| 61 | + |
| 62 | +## Extending `OpenIdDictApplicationManagerBase` |
| 63 | + |
| 64 | +Create a new client for production use by extending the `OpenIdDictApplicationManagerBase`. |
| 65 | + |
| 66 | +```csharp |
| 67 | +public class CustomApplicationManager : OpenIdDictApplicationManagerBase |
| 68 | +{ |
| 69 | + public CustomApplicationManager(IOpenIddictApplicationManager applicationManager) |
| 70 | + : base(applicationManager) |
| 71 | + { |
| 72 | + } |
| 73 | + |
| 74 | + public async Task EnsureCustomApplicationAsync(string clientId, Uri redirectUri, CancellationToken cancellationToken = default) |
| 75 | + { |
| 76 | + if (!redirectUri.IsAbsoluteUri) |
| 77 | + { |
| 78 | + throw new ArgumentException("The provided URL must be an absolute URL.", nameof(redirectUri)); |
| 79 | + } |
| 80 | + |
| 81 | + var clientDescriptor = new OpenIddictApplicationDescriptor |
| 82 | + { |
| 83 | + DisplayName = "Custom Application", |
| 84 | + ClientId = clientId, |
| 85 | + RedirectUris = { redirectUri }, |
| 86 | + ClientType = OpenIddictConstants.ClientTypes.Public, |
| 87 | + Permissions = { |
| 88 | + OpenIddictConstants.Permissions.Endpoints.Authorization, |
| 89 | + OpenIddictConstants.Permissions.Endpoints.Token, |
| 90 | + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, |
| 91 | + OpenIddictConstants.Permissions.ResponseTypes.Code |
| 92 | + } |
| 93 | + }; |
| 94 | + |
| 95 | + await CreateOrUpdate(clientDescriptor, cancellationToken); |
| 96 | + } |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +The above code allows you to define a new custom client. This client will not interfere with the existing `umbraco-back-office` client, ensuring smooth integration and avoiding callback conflicts. |
| 101 | + |
| 102 | +# Minimal API Implementation |
| 103 | + |
| 104 | +To set up a Minimal API that integrates the custom client, follow these steps: |
| 105 | + |
| 106 | +## Creating the Minimal API Application |
| 107 | + |
| 108 | +Below is a complete setup for using Minimal API to create and manage custom OAuth clients for the Umbraco Management API. |
| 109 | + |
| 110 | +```csharp |
| 111 | +builder.Services.AddScoped<CustomApplicationManager>(provider => |
| 112 | +{ |
| 113 | + var applicationManager = provider.GetRequiredService<IOpenIddictApplicationManager>(); |
| 114 | + return new CustomApplicationManager(applicationManager); |
| 115 | +}); |
| 116 | + |
| 117 | +app.MapPost("/create-client", async (ClientModel model, CustomApplicationManager applicationManager) => |
| 118 | +{ |
| 119 | + try |
| 120 | + { |
| 121 | + if (string.IsNullOrEmpty(model.ClientId)) |
| 122 | + return Results.BadRequest("Client ID is required."); |
| 123 | + |
| 124 | + if (!Uri.TryCreate(model.RedirectUri, UriKind.Absolute, out var redirectUri)) |
| 125 | + return Results.BadRequest("Invalid redirect URI."); |
| 126 | + |
| 127 | + await applicationManager.EnsureCustomApplicationAsync(model.ClientId, redirectUri); |
| 128 | + return Results.Ok("Client created/updated successfully."); |
| 129 | + } |
| 130 | + catch (Exception ex) |
| 131 | + { |
| 132 | + return Results.Problem(ex.Message); |
| 133 | + } |
| 134 | +}).WithName("CreateClient"); |
| 135 | + |
| 136 | +app.MapGet("/login", (Auth auth, IConfiguration config, IBackOfficeApplicationManager backOfficeApplicationManager) => |
| 137 | +{ |
| 138 | + var baseUrl = config["Umbraco:BaseUrl"]; |
| 139 | + var authorizationUrl = auth.GetAuthorizationUrl(); |
| 140 | + return Results.Redirect(baseUrl + authorizationUrl); |
| 141 | +}); |
| 142 | + |
| 143 | +app.MapGet("/callback", async (Auth auth, HttpContext httpContext, IConfiguration configuration) => |
| 144 | +{ |
| 145 | + var code = httpContext.Request.Query["code"]; |
| 146 | + var state = httpContext.Request.Query["state"]; |
| 147 | + if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state)) |
| 148 | + { |
| 149 | + return Results.BadRequest("Invalid callback parameters"); |
| 150 | + } |
| 151 | + try |
| 152 | + { |
| 153 | + var tokenResponse = await auth.HandleCallback(code, state); |
| 154 | + // |
| 155 | + return Results.Redirect("/dashboard"); |
| 156 | + } |
| 157 | + catch (Exception ex) |
| 158 | + { |
| 159 | + return Results.BadRequest($"Authentication failed: {ex.Message}"); |
| 160 | + } |
| 161 | +}); |
| 162 | + |
| 163 | +public class ClientModel |
| 164 | +{ |
| 165 | + public string ClientId { get; set; } |
| 166 | + public string RedirectUri { get; set; } |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +This implementation demonstrates how to use Minimal API to manage OAuth clients dynamically, allowing better integration into production workflows. |
| 171 | + |
| 172 | +# Configuring Authorization in Production |
| 173 | + |
| 174 | +To configure authorization using the custom client: |
| 175 | + |
| 176 | +1. Update your `appsettings.json` file: |
| 177 | + |
| 178 | +```json |
| 179 | +{ |
| 180 | + "BaseUrl": "https://your-production-domain", |
| 181 | + "ClientId": "newclientId", // Set to the new custom client ID you created |
| 182 | + "AuthorizationEndpoint": "/umbraco/management/api/v1/security/back-office/authorize", |
| 183 | + "TokenEndpoint": "/umbraco/management/api/v1/security/back-office/token", |
| 184 | + "RedirectUri": "https://your-production-domain/callback" // Callback URL for your newly created client ID |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +2. Use the custom client manager endpoint (`/create-client`) to create a new client for use in production. |
| 189 | + |
| 190 | +3. Handle token retrieval and secure storage in your application. Store tokens securely to avoid exposure, for instance by using HTTP-only cookies. |
| 191 | + |
| 192 | +# Common Pitfalls and Troubleshooting |
| 193 | + |
| 194 | +## Callback Interference with Back Office |
| 195 | + |
| 196 | +If the `umbraco-back-office` client causes callback conflicts, use a custom client with a distinct redirect URI to prevent overlap with backoffice authentication. |
0 commit comments