Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PAR - support processed params in authorize endpoint #1566

Merged
merged 4 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading