|
| 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, making testing easy. |
| 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 | +```json |
| 42 | +"Umbraco": { |
| 43 | + "CMS": { |
| 44 | + "Security": { |
| 45 | + "AuthorizeCallbackPathName": "/callback" |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | +``` |
| 50 | + |
| 51 | +This configuration allows you to specify a custom callback path for OAuth, but note that this will interfere with the default back-office callback path, making back-office not accessible. |
| 52 | + |
| 53 | +# Setting up Production-Local Authorization |
| 54 | + |
| 55 | +In a production environment, Swagger UI is disabled, and only the `umbraco-back-office` client can be used. This requires a more advanced approach. |
| 56 | + |
| 57 | +# Creating a Custom Client ID |
| 58 | + |
| 59 | +To avoid conflicts with the back-office, a new client should be created. Below are the steps to set up a custom client using a Minimal API: |
| 60 | + |
| 61 | +## Extending `OpenIdDictApplicationManagerBase` |
| 62 | + |
| 63 | +Create a new client for production use by extending the `OpenIdDictApplicationManagerBase`. |
| 64 | + |
| 65 | +```csharp |
| 66 | +public class CustomApplicationManager : OpenIdDictApplicationManagerBase |
| 67 | +{ |
| 68 | + public CustomApplicationManager(IOpenIddictApplicationManager applicationManager) |
| 69 | + : base(applicationManager) |
| 70 | + { |
| 71 | + } |
| 72 | + |
| 73 | + public async Task EnsureCustomApplicationAsync(string clientId, Uri redirectUri, CancellationToken cancellationToken = default) |
| 74 | + { |
| 75 | + if (!redirectUri.IsAbsoluteUri) |
| 76 | + { |
| 77 | + throw new ArgumentException("The provided URL must be an absolute URL.", nameof(redirectUri)); |
| 78 | + } |
| 79 | + |
| 80 | + var clientDescriptor = new OpenIddictApplicationDescriptor |
| 81 | + { |
| 82 | + DisplayName = "Custom Application", |
| 83 | + ClientId = clientId, |
| 84 | + RedirectUris = { redirectUri }, |
| 85 | + ClientType = OpenIddictConstants.ClientTypes.Public, |
| 86 | + Permissions = { |
| 87 | + OpenIddictConstants.Permissions.Endpoints.Authorization, |
| 88 | + OpenIddictConstants.Permissions.Endpoints.Token, |
| 89 | + OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, |
| 90 | + OpenIddictConstants.Permissions.ResponseTypes.Code |
| 91 | + } |
| 92 | + }; |
| 93 | + |
| 94 | + await CreateOrUpdate(clientDescriptor, cancellationToken); |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +The above code allows you to define a new custom client that does not interfere with the existing `umbraco-back-office` client, ensuring smoother integration and avoiding callback conflicts. |
| 100 | + |
| 101 | +# Minimal API Implementation |
| 102 | + |
| 103 | +To set up a Minimal API that integrates the custom client, follow these steps: |
| 104 | + |
| 105 | +## Creating the Minimal API Application |
| 106 | + |
| 107 | +Below is a complete setup for using Minimal API to create and manage custom OAuth clients for the Umbraco Management API. |
| 108 | + |
| 109 | +```csharp |
| 110 | +builder.Services.AddScoped<CustomApplicationManager>(provider => |
| 111 | +{ |
| 112 | + var applicationManager = provider.GetRequiredService<IOpenIddictApplicationManager>(); |
| 113 | + return new CustomApplicationManager(applicationManager); |
| 114 | +}); |
| 115 | + |
| 116 | +app.MapPost("/create-client", async (ClientModel model, CustomApplicationManager applicationManager) => |
| 117 | +{ |
| 118 | + try |
| 119 | + { |
| 120 | + if (string.IsNullOrEmpty(model.ClientId)) |
| 121 | + return Results.BadRequest("Client ID is required."); |
| 122 | + |
| 123 | + if (!Uri.TryCreate(model.RedirectUri, UriKind.Absolute, out var redirectUri)) |
| 124 | + return Results.BadRequest("Invalid redirect URI."); |
| 125 | + |
| 126 | + await applicationManager.EnsureCustomApplicationAsync(model.ClientId, redirectUri); |
| 127 | + return Results.Ok("Client created/updated successfully."); |
| 128 | + } |
| 129 | + catch (Exception ex) |
| 130 | + { |
| 131 | + return Results.Problem(ex.Message); |
| 132 | + } |
| 133 | +}).WithName("CreateClient"); |
| 134 | + |
| 135 | +app.MapGet("/login", (Auth auth, IConfiguration config, IBackOfficeApplicationManager backOfficeApplicationManager) => |
| 136 | +{ |
| 137 | + var baseUrl = config["Umbraco:BaseUrl"]; |
| 138 | + var authorizationUrl = auth.GetAuthorizationUrl(); |
| 139 | + return Results.Redirect(baseUrl + authorizationUrl); |
| 140 | +}); |
| 141 | + |
| 142 | +app.MapGet("/callback", async (Auth auth, HttpContext httpContext, IConfiguration configuration) => |
| 143 | +{ |
| 144 | + var code = httpContext.Request.Query["code"]; |
| 145 | + var state = httpContext.Request.Query["state"]; |
| 146 | + if (string.IsNullOrEmpty(code) || string.IsNullOrEmpty(state)) |
| 147 | + { |
| 148 | + return Results.BadRequest("Invalid callback parameters"); |
| 149 | + } |
| 150 | + try |
| 151 | + { |
| 152 | + var tokenResponse = await auth.HandleCallback(code, state); |
| 153 | + // |
| 154 | + return Results.Redirect("/dashboard"); |
| 155 | + } |
| 156 | + catch (Exception ex) |
| 157 | + { |
| 158 | + return Results.BadRequest($"Authentication failed: {ex.Message}"); |
| 159 | + } |
| 160 | +}); |
| 161 | + |
| 162 | +public class ClientModel |
| 163 | +{ |
| 164 | + public string ClientId { get; set; } |
| 165 | + public string RedirectUri { get; set; } |
| 166 | +} |
| 167 | +``` |
| 168 | + |
| 169 | +This implementation demonstrates how to use Minimal API to manage OAuth clients dynamically, allowing better integration into production workflows. |
| 170 | + |
| 171 | +# Configuring Authorization in Production |
| 172 | + |
| 173 | +To configure authorization using the custom client: |
| 174 | + |
| 175 | +1. Update your `appsettings.json` file: |
| 176 | + |
| 177 | +```json |
| 178 | +{ |
| 179 | + "BaseUrl": "https://your-production-domain", |
| 180 | + "ClientId": "newclientId", |
| 181 | + "AuthorizationEndpoint": "/umbraco/management/api/v1/security/back-office/authorize", |
| 182 | + "TokenEndpoint": "/umbraco/management/api/v1/security/back-office/token", |
| 183 | + "RedirectUri": "https://your-production-domain/callback" |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +2. Use the custom client manager endpoint (`/create-client`) to create a new client for use in production. |
| 188 | + |
| 189 | +3. Handle token retrieval and secure storage in your application. Store tokens securely to avoid exposure, for instance by using HTTP-only cookies. |
| 190 | + |
| 191 | +# Common Pitfalls and Troubleshooting |
| 192 | + |
| 193 | +## Callback Interference with Back Office |
| 194 | + |
| 195 | +If using the `umbraco-back-office` client causes callback conflicts, ensure that your custom client has a distinct redirect URI and does not overlap with back-office authentication settings. |
| 196 | + |
0 commit comments