Unable to open BackOffice after new install

This is my first try with Umbraco. I have installed the latest version on my local Windows machine according to the docs.

When I start with dotnet run, I see a welcome page in the browser (on both ports shown in the command line). However, when I click on the Open Umbraco button, I get the following error message:

error:invalid_request
error_description:The specified 'redirect_uri' is not valid for this client application.
error_uri:https://documentation.openiddict.com/errors/ID2043

The URL looks like this:

https://localhost:44334/umbraco/management/api/v1/security/back-office/authorize?redirect_uri=https%3A%2F%2Flocalhost%3A44334%2Fumbraco%2Foauth_complete&client_id=umbraco-back-office&response_type=code&state=Xf8P0V5V9Y&scope=offline_access&prompt=consent&access_type=offline&code_challenge=snXL3fsdYvHjla49dZlqV1p7H4uU3arHMK3lZj_pOWU&code_challenge_method=S256

I suspect that this has to do with encrypting transport between the browser and Kestrell, but I am not sure.

I have tried to switch this behavoir off by setting the runtime mode to developer in appsettings.json (docs), but that does not help.

What causes this error and how can I fix it on my local Windows machine?


This is a companion discussion topic for the original entry at https://our.umbraco.com/forum/114233-unable-to-open-backoffice-after-new-install

It looks there is a bug here as where these properties are stored looks to be an array, but everytime you restart the application it overwrites the array with one URL

[dbo].[umbracoOpenIddictApplications]

PostLogoutRedirectUris
RedirectUris

As a work around to get this working for now we have added this service to the program.cs to correct the two fields above, and allow us to share a dev database between multiple URLs

using System.Data;
using System.Text.Json;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

public class OpenIdUpdateService(IServiceProvider serviceProvider, ILogger<OpenIdUpdateService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Ensure the application is fully started before running SQL
        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); // Small delay for safety

        using (var scope = serviceProvider.CreateScope())
        {
            var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
            string connectionString = configuration?.GetConnectionString("UmbracoDbDSN") ?? string.Empty;

            if (string.IsNullOrEmpty(connectionString))
            {
                logger.LogError("[OpenIdUpdateService] Database connection string is missing.");
                return;
            }

            // Get values from appsettings.json
            var postLogoutRedirectUris = configuration.GetSection("OpenIdSettings:PostLogoutRedirectUris").Get<List<string>>();
            var redirectUris = configuration.GetSection("OpenIdSettings:RedirectUris").Get<List<string>>();

            if (postLogoutRedirectUris == null || redirectUris == null)
            {
                logger.LogError("[OpenIdUpdateService] OpenID settings are missing in appsettings.json.");
                return;
            }

            // Convert lists to JSON format for SQL
            string postLogoutRedirectUrisJson = JsonSerializer.Serialize(postLogoutRedirectUris);
            string redirectUrisJson = JsonSerializer.Serialize(redirectUris);

            // Corrected SQL statement with proper JSON formatting
            string sql = @"
                UPDATE [dbo].[umbracoOpenIddictApplications] 
                SET PostLogoutRedirectUris = @PostLogoutRedirectUris,
                RedirectUris = @RedirectUris
                WHERE ClientId = 'umbraco-back-office';
            ";

            try
            {
                using (SqlConnection conn = new SqlConnection(connectionString))
                {
                    await conn.OpenAsync(stoppingToken);

                    using (SqlCommand cmd = new SqlCommand(sql, conn))
                    {
                        cmd.CommandType = CommandType.Text;
                        cmd.Parameters.AddWithValue("@PostLogoutRedirectUris", postLogoutRedirectUrisJson);
                        cmd.Parameters.AddWithValue("@RedirectUris", redirectUrisJson);

                        int rowsAffected = await cmd.ExecuteNonQueryAsync(stoppingToken);
                        logger.LogInformation($"[OpenIdUpdateService] Database update successful. Rows affected: {rowsAffected}");
                    }
                }
            }
            catch (Exception ex)
            {
                logger.LogError($"[OpenIdUpdateService] Error updating database: {ex.Message}");
            }
        }
    }
}

register it in program.cs

// Register OpenIdUpdateService to run after startup
// This is a temporary fix (hack) to resolve an issue in Umbraco 15 overwriting the OpenId return URLs
builder.Services.AddHostedService<OpenIdUpdateService>();

Add all the URLs you wish to whitelist to appsettings.json

    "OpenIdSettings": {
        "PostLogoutRedirectUris": [
            "https://dev.YOURSITE.com/umbraco/oauth_complete",
            "https://dev.YOURSITE.com/umbraco/logout",
            "https://YOURSITE.local/umbraco/oauth_complete",
            "https://YOURSITE.local/umbraco/logout",
            "https://YOURSITE-development.azurewebsites.net/umbraco/oauth_complete",
            "https://YOURSITE-development.azurewebsites.net/umbraco/logout"
        ],
        "RedirectUris": [
            "https://dev.YOURSITE.com/umbraco/oauth_complete",
            "https://YOURSITE.local/umbraco/oauth_complete",
            "https://YOURSITE-development.azurewebsites.net/umbraco/oauth_complete"
        ]
    }

This all feels very hacky, but it got us up and running again so hope it helps someone else until we figure out a neater way

I realise this is a thread from the old forum with a new reply, but I just wanted to chime in that 15.3.0 will see an improvement in this area, where we are going to support multiple backoffice hosts. This means that running the site on localhost will register any URL that tries to log in.

You can additionally overwrite the BackOfficeHost URL through appsettings.json if you want to specify a specific URL - this is particularly useful in environments where the calling URL isn’t obvious from the get-go, such as Docker or Azure App Services, where you might not want the internal URL to be recognized.

The key is located in Umbraco:Cms:Security and is documented here: Class SecuritySettings | Umbraco c# Api docs

Oh nice! This will β€˜fix’ my issue that when using deployment slots in Umbraco, one of the instances cannot log into the backoffice because it’s redirect_url is not known :slight_smile:

Awesome! You might also consider setting the application URL additionally or instead (I believe the BackOfficeHost falls back to that if set).

Application URL:

Corresponding health check:

The ApplicationUrl is a requirement for production runtime mode, so all good :slight_smile:

1 Like