Skip to content

Commit

Permalink
Merge pull request #1566 from DuendeSoftware/joe/par-prompt-query-params
Browse files Browse the repository at this point in the history
PAR - support processed params in authorize endpoint
  • Loading branch information
brockallen authored Jun 3, 2024
2 parents fa3c9cc + 89736be commit 1190605
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ public async Task WriteHttpResponse(AuthorizeInteractionPageResult result, HttpC
returnUrl = returnUrl
.AddQueryString(OidcConstants.AuthorizeRequest.RequestUri, requestUri)
.AddQueryString(OidcConstants.AuthorizeRequest.ClientId, result.Request.ClientId);
var processedPrompt = result.Request.Raw[Constants.ProcessedPrompt];
if (processedPrompt != null)
{
returnUrl = returnUrl.AddQueryString(Constants.ProcessedPrompt, processedPrompt);
}
var processedMaxAge = result.Request.Raw[Constants.ProcessedMaxAge];
if (processedMaxAge != null)
{
returnUrl = returnUrl.AddQueryString(Constants.ProcessedMaxAge, processedMaxAge);
}
}
else
{
Expand Down
13 changes: 13 additions & 0 deletions src/IdentityServer/Validation/Default/RequestObjectValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,20 @@ private static bool IsParRequestUri(string requestUri)
// Record the reference value, so we can know that PAR did happen
request.PushedAuthorizationReferenceValue = GetReferenceValue(request);
// Copy the PAR into the raw request so that validation will use the pushed parameters
// But keep the query parameters we add that indicate that we have processed
// prompt and max_age, as those are not pushed
var processedPrompt = request.Raw[Constants.ProcessedPrompt];
var processedMaxAge = request.Raw[Constants.ProcessedMaxAge];

request.Raw = pushedAuthorizationRequest.PushedParameters;
if (processedPrompt != null)
{
request.Raw[Constants.ProcessedPrompt] = processedPrompt;
}
if (processedMaxAge != null)
{
request.Raw[Constants.ProcessedMaxAge] = processedMaxAge;
}

var bindingError = ValidatePushedAuthorizationBindingToClient(pushedAuthorizationRequest, request);
if (bindingError != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class IdentityServerPipeline
public const string LoginPage = BaseUrl + "/account/login";
public const string LogoutPage = BaseUrl + "/account/logout";
public const string ConsentPage = BaseUrl + "/account/consent";
public const string CreateAccountPage = BaseUrl + "/account/create";

public const string ErrorPage = BaseUrl + "/home/error";

public const string DeviceAuthorization = BaseUrl + "/connect/deviceauthorization";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using FluentAssertions;
using IdentityModel;
using IntegrationTests.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -190,6 +191,63 @@ public async Task pushed_authorization_with_a_request_uri_fails(string requestUr
.Should().Be(OidcConstants.AuthorizeErrors.InvalidRequest);
}


[Theory]
[InlineData("prompt", "login")]
[InlineData("prompt", "select_account")]
[InlineData("prompt", "create")]
[InlineData("max_age", "0")]
public async Task prompt_login_can_be_used_with_pushed_authorization(string parameterName, string parameterValue)
{
// Login before we start (we expect to still be prompted to login because of the prompt param)
_mockPipeline.Options.UserInteraction.CreateAccountUrl = IdentityServerPipeline.CreateAccountPage;
_mockPipeline.Options.UserInteraction.PromptValuesSupported.Add(OidcConstants.PromptModes.Create);
await _mockPipeline.LoginAsync("bob");
_mockPipeline.BrowserClient.AllowAutoRedirect = false;

// Push Authorization
var expectedCallback = _client.RedirectUris.First();
var expectedState = "123_state";
var (parJson, statusCode) = await _mockPipeline.PushAuthorizationRequestAsync(
redirectUri: expectedCallback,
state: expectedState,
extra: new Dictionary<string, string>
{
{ parameterName, parameterValue }
}
);
statusCode.Should().Be(HttpStatusCode.Created);

// Authorize using pushed request
var authorizeUrl = _mockPipeline.CreateAuthorizeUrl(
clientId: "client1",
extra: new
{
request_uri = parJson.RootElement.GetProperty("request_uri").GetString()
});
var authorizeResponse = await _mockPipeline.BrowserClient.GetAsync(authorizeUrl);

// Verify that authorize redirects to login
authorizeResponse.Should().Be302Found();
var isPromptCreate = parameterName == "prompt" && parameterValue == "create";
var expectedLocation = isPromptCreate ? IdentityServerPipeline.CreateAccountPage : IdentityServerPipeline.LoginPage;
authorizeResponse.Headers.Location.ToString().ToLower().Should().Match($"{expectedLocation.ToLower()}*");

// Verify that the UI prompts the user at this point
var uiResponse = await _mockPipeline.BrowserClient.GetAsync(authorizeResponse.Headers.Location);
uiResponse.Should().Be200Ok();

// Now login and return to the return url we were given
var returnPath = isPromptCreate ? _mockPipeline.CreateAccountReturnUrl : _mockPipeline.LoginReturnUrl;
var returnUrl = new Uri(new Uri(IdentityServerPipeline.BaseUrl), returnPath);
await _mockPipeline.LoginAsync("bob");
var authorizeCallbackResponse = await _mockPipeline.BrowserClient.GetAsync(returnUrl);

// The authorize callback should continue back to the application (the prompt parameter is processed so we don't go back to the UI)
authorizeCallbackResponse.Should().Be302Found();
authorizeCallbackResponse.Headers.Location.Should().Be(expectedCallback);
}

private void ConfigureScopesAndResources()
{
_mockPipeline.IdentityScopes.AddRange(new IdentityResource[] {
Expand Down

0 comments on commit 1190605

Please sign in to comment.