Triggering User Registration via OpenID Connect with Duende IdentityServer

Maarten Balliauw |

When you separate identity management from your application, login is handled through OpenID Connect. But what about registration? A community discussion recently highlighted a common scenario: you want a "Register" link in your application that takes users straight to a registration page on Duende IdentityServer, skipping the login screen.

OpenID Connect has a standardized answer for this. Initiating User Registration via OpenID Connect 1.0 defines a prompt=create parameter that tells the identity provider to show account creation instead of login. Duende IdentityServer has supported this since version 6.3. Let's look at the spec and then walk through how to implement it with Dudende IdentityServer.

The prompt Parameter in OpenID Connect

The prompt parameter is part of the authorization request and tells the OpenID Provider (OP) what kind of user interaction to present. The core specification defines these values:

Value Behavior
none No UI is shown. If authentication or consent is required, an error is returned.
login Force the user to re-authenticate, even if they have an active session.
consent Prompt the user for consent, even if previously granted.
select_account Ask the user to choose an account when multiple sessions exist.

These values have been around since OpenID Connect Core 1.0, but none of them address user registration.

The Initiating User Registration via OpenID Connect 1.0 specification, finalized in December 2022, fills that gap. When a client includes prompt=create in the authorization request, it tells the OP that the user wants to register a new account rather than sign in.

A standard authorization code flow request with prompt=create:

GET /connect/authorize?response_type=code
    &client_id=my-web-app
    &redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fsignin-oidc
    &scope=openid%20profile
    &prompt=create HTTP/1.1
Host: identity.example.com

A few things to know about this spec:

  • It's a hint. The OP decides how to handle it. It might show a registration form, redirect to an external registration service, or present the login page with a registration option highlighted.
  • The flow must be completed with tokens. The client should not assume a new account was created unless it receives a valid id_token (and optionally an access_token) back. The registration flow ends the same way as a login flow: with a redirect back to the client, carrying authorization parameters.
  • Discovery support. An OP that supports prompt=create advertises this through the prompt_values_supported field in its discovery document, so clients can detect support before using it.

Configuring Duende IdentityServer for prompt=create

To enable prompt=create, configure the CreateAccountUrl option in your IdentityServer setup. This setting tells IdentityServer where to redirect users when prompt=create is received, and adds create to the discovery document's prompt_values_supported array.

// Program.cs (IdentityServer)
builder.Services.AddIdentityServer(options =>
{
    options.UserInteraction.CreateAccountUrl = "/Account/Register";
})

When a client sends an authorization request with prompt=create, IdentityServer redirects the user to /Account/Register (or whatever URL you configure) instead of the login page. The registration page receives the same returnUrl query parameter as the login page, so after registration completes, you redirect the user back into the authorization flow.

Whether you use ASP.NET Identity or have a custom user store, you'll want to use the Interaction Service to inspect the authorization context:

// Account/Register.cshtml.cs (IdentityServer)
public class RegisterModel : PageModel
{
    private readonly IIdentityServerInteractionService _interaction;

    public RegisterModel(IIdentityServerInteractionService interaction)
    {
        _interaction = interaction;
    }

    public async Task<IActionResult> OnGet(string returnUrl)
    {
        var context = await _interaction.GetAuthorizationContextAsync(returnUrl);

        // context contains client info, scopes, etc.
        // Use it to customize the registration experience per client if needed

        return Page();
    }

    public async Task<IActionResult> OnPost(string returnUrl)
    {
        // Create the user account (e.g., using ASP.NET Core Identity)
        // ...

        // Sign the user in.
        //
        // Note that there may be other steps here, like account confirmation.
        // This may interrupt the flow back to the app.
        // ...

        // Continue the authorization flow
        return Redirect(returnUrl);
    }
}

The AuthorizationRequest returned by GetAuthorizationContextAsync gives you access to the client ID, requested scopes, and PromptMode (which will be create in this case). Use this to customize the registration experience per client, pre-fill fields based on login_hint, or apply client-specific registration policies.

Note that the registration flow may be more complex, and could include email confirmation or account activation steps. This may mean keeping the authorization context around through the request URL, and returning to the application only after those steps are successful.

Triggering Registration from a Client Application

On the client side, you need a way to send the user into the registration flow. The approach is the same as for a "Login" link: create an endpoint that challenges the OpenID Connect authentication scheme, but with prompt=create added to the request.

The following code snippet uses a minimal API endpoint, but you could also use Razor Pages or MVC. Use the RedirectUri property on AuthenticationProperties to control where the user lands after the flow completes.

// Program.cs (Client application)
app.MapGet("/register", (string? returnUrl) =>
{
    var props = new AuthenticationProperties
    {
        RedirectUri = returnUrl ?? "/"
    };

    // OpenIdConnectParameterNames.Prompt is a constant for "prompt"
    props.SetParameter(OpenIdConnectParameterNames.Prompt, "create");

    // NOTE: if you have multiple schemes configured,
    // you will want to add the auth scheme name here as well.
    return Results.Challenge(props);
});

Now you can place a registration link anywhere in your application, for example:

<a href="/register">Create an account</a>
<a href="/register?returnUrl=/dashboard">Sign up and go to dashboard</a>

The Complete Flow

When a user clicks "Register" in your application:

  1. The user clicks "Register", which hits your /register endpoint.
  2. Your app challenges the oidc authentication scheme with prompt=create in the authentication properties.
  3. The ASP.NET Core OpenID Connect handler builds the authorization request URL and redirects to IdentityServer's /connect/authorize endpoint with prompt=create.
  4. IdentityServer sees prompt=create and redirects to the configured CreateAccountUrl, passing along the returnUrl.
  5. The user fills out the registration form and creates their account.
  6. After registration, the user is signed in and redirected back through the authorization flow via the returnUrl.
  7. IdentityServer issues tokens and redirects back to your client application's callback endpoint (/signin-oidc).
  8. Your client application processes the tokens, establishes a session, and redirects to the RedirectUri you specified.

Your client application never needs to know about registration forms, user stores, or password policies. It sends prompt=create and IdentityServer handles the rest.

You could link directly to IdentityServer's registration page, but prompt=create is a better fit. Your client code doesn't need to know the registration page URL. It uses the same OpenID Connect flow it already uses for login, with a different prompt value. That makes it portable: if you switch identity providers or change the registration URL, your client code stays the same.

Registration also happens within a proper authorization flow, so after account creation the user gets tokens and a session with no additional authentication step. And because prompt=create is advertised through prompt_values_supported in the discovery document, clients can detect support before showing a "Register" button at all.

Wrapping Up

When you centralize identity management in Duende IdentityServer, you want login and registration to go through the same standardized OpenID Connect flow. The prompt=create parameter, defined in the Initiating User Registration via OpenID Connect 1.0 specification, gives your client applications a way to request account creation without knowing anything about how or where registration happens. Your client sends prompt=create, IdentityServer routes the user to your registration page, and the rest of the authorization flow proceeds as usual.

To set this up, you need three things: configure UserInteraction.CreateAccountUrl in IdentityServer to point to your registration page, use IIdentityServerInteractionService on that page to access the authorization context and redirect back into the flow after account creation, and create an endpoint in your client application that challenges the OIDC scheme with prompt=create in the AuthenticationProperties.


Thanks for stopping by!

We hope this post helped you on your identity and security journey. If you need a hand with implementation, our docs are always open. For everything else, come hang out with the team and other developers on GitHub.

If you want to get early access to new features and products while collaborating with experts in security and identity standards, join us in our Duende Product Insiders program. And if you prefer your tech content in video form, our YouTube channel is the place to be. Don't forget to like and subscribe!

Questions? Comments? Just want to say hi? Leave a comment below and let's start a conversation.