Skip to content
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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PatchVersion>0</PatchVersion>
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
<PreReleaseVersionIteration>4</PreReleaseVersionIteration>
<PreReleaseVersionIteration>5</PreReleaseVersionIteration>
<PreReleaseBrandingLabel>Preview $(PreReleaseVersionIteration)</PreReleaseBrandingLabel>
<StabilizePackageVersion Condition=" '$(StabilizePackageVersion)' == '' ">false</StabilizePackageVersion>
<DotNetFinalVersionKind Condition=" '$(StabilizePackageVersion)' == 'true' ">release</DotNetFinalVersionKind>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,28 @@
</Environment>
</Provider>

<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█████ ██ ▄▄▄ █▄ ▄██ ▀██ ██ ▄▄▀██ ▄▄ ██ ▄▄ ██
█████ ██ ███ ██ ███ █ █ ██ ▀▀▄██ ▀▀ ██ █▀▀██
██ ▀▀ ██ ▀▀▀ █▀ ▀██ ██▄ ██ ██ ██ █████ ▀▀▄██
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-->

<Provider Name="JoinRpg" Id="698f413f-21e1-4be1-9b7f-c96f91bbf41d"
Documentation="https://github.com/joinrpg/joinrpg-net/blob/master/docs/adr004-oauth-server.md">
<!--
Note: the "email", "phone" and "profile" scopes are not strictly required but are requested
by default to receive the corresponding standard claims from the userinfo endpoint.
-->

<Environment Issuer="https://id.joinrpg.ru/">
<Scope Name="email" Default="true" Required="false" />
<Scope Name="phone" Default="true" Required="false" />
<Scope Name="profile" Default="true" Required="false" />
</Environment>
</Provider>

<!--
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
█████ ██ ██ ██ ▄▀▄ ██ ▄▄ ██ ▄▄▀██ █████ ▄▄▄ ██ ██ ██ ▄▄▀██
Expand Down
12 changes: 0 additions & 12 deletions src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -713,12 +713,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default presenter validation.
if (context.ValidPresenters.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any presenter attached, return an error.
var presenters = context.Principal.GetPresenters();
if (presenters.IsDefaultOrEmpty)
Expand Down Expand Up @@ -773,12 +767,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default audience validation.
if (context.ValidAudiences.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any audience attached, return an error.
var audiences = context.Principal.GetAudiences();
if (audiences.IsDefaultOrEmpty)
Expand Down
12 changes: 12 additions & 0 deletions src/OpenIddict.Client/OpenIddictClientHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.FrontchannelAccessToken,
ValidTokenTypes = { TokenTypeIdentifiers.AccessToken }
};
Expand Down Expand Up @@ -2207,6 +2209,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.AuthorizationCode,
ValidTokenTypes = { TokenTypeIdentifiers.Private.AuthorizationCode }
};
Expand Down Expand Up @@ -3838,6 +3842,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.BackchannelAccessToken,
ValidTokenTypes = { TokenTypeIdentifiers.AccessToken }
};
Expand Down Expand Up @@ -3907,6 +3913,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.IssuedToken,
ValidTokenTypes = { context.IssuedTokenType! }
};
Expand Down Expand Up @@ -3978,6 +3986,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.RefreshToken,
ValidTokenTypes = { TokenTypeIdentifiers.RefreshToken }
};
Expand Down Expand Up @@ -4480,6 +4490,8 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
DisablePresenterValidation = true,
Token = context.UserInfoToken,
ValidTokenTypes = { TokenTypeIdentifiers.Private.UserInfoToken }
};
Expand Down
14 changes: 9 additions & 5 deletions src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,17 +412,21 @@ public ValueTask HandleAsync(ValidateTokenRequestContext context)
{
ArgumentNullException.ThrowIfNull(context);

if (!context.Request.IsAuthorizationCodeGrantType() && !context.Request.IsClientCredentialsGrantType())
if (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsClientCredentialsGrantType() &&
!context.Request.IsDeviceCodeGrantType())
{
return ValueTask.CompletedTask;
}

// Reject grant_type=authorization_code and grant_type=client_credentials requests that
// don't specify a client_id or a client_assertion, as the client identity MUST be sent
// Reject grant_type=authorization_code, grant_type=client_credentials and
// grant_type=urn:ietf:params:oauth:grant-type:device_code requests that don't
// specify a client_id or a client_assertion, as the client identity MUST be sent
// by the client application (even when using mTLS OAuth 2.0 client authentication).
//
// See https://tools.ietf.org/html/rfc6749#section-4.1.3
// and https://tools.ietf.org/html/rfc6749#section-4.4.1 for more information.
// See https://tools.ietf.org/html/rfc6749#section-4.1.3,
// https://tools.ietf.org/html/rfc6749#section-4.4.1 and
// https://tools.ietf.org/html/rfc8628#section-3.4 for more information.
if (string.IsNullOrEmpty(context.Request.ClientId) &&
string.IsNullOrEmpty(context.Request.ClientAssertion))
{
Expand Down
12 changes: 0 additions & 12 deletions src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -999,12 +999,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default presenter validation.
if (context.ValidPresenters.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any presenter attached, return an error.
var presenters = context.Principal.GetPresenters();
if (presenters.IsDefaultOrEmpty)
Expand Down Expand Up @@ -1059,12 +1053,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default audience validation.
if (context.ValidAudiences.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any audience attached, return an error.
var audiences = context.Principal.GetAudiences();
if (audiences.IsDefaultOrEmpty)
Expand Down
48 changes: 32 additions & 16 deletions src/OpenIddict.Server/OpenIddictServerHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
// For more information, see
// https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication and
// https://datatracker.ietf.org/doc/html/rfc7523#section-3.
if (context.ClientAssertionPrincipal.GetAudiences() is not [_])
if (context.ClientAssertionPrincipal.GetAudiences() is not [{ Length: > 0 }])
{
context.Reject(
error: Errors.InvalidRequest,
Expand Down Expand Up @@ -814,15 +814,21 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)

static bool ValidateClaimGroup(string name, List<Claim> values) => name switch
{
// The following claims MUST be represented as unique strings.
//
// Important: client assertions with multiple audiences was initially deliberately supported by
// the OpenID Connect and Assertion Framework for OAuth 2.0 Client Authentication specifications.
// Since 2025, using multiple audiences is no longer allowed for security reasons. As such, the
// "aud" claim present in client assertions MUST always be represented as a single string.
// The following claims MUST be represented as unique strings or array of strings.
//
// See https://www.ietf.org/archive/id/draft-ietf-oauth-rfc7523bis-01.html#section-4 for more information.
Claims.Audience or Claims.AuthorizedParty or Claims.Issuer or Claims.JwtId or Claims.Subject
// Note: the initial version of the "Updates to Audience Values for OAuth 2.0 Authorization Servers"
// specification initially required that the "aud" claim be represented as a unique string but more
// recent versions of the specification allow the "aud" claim to be represented as a JSON array of strings.
Claims.Audience => values.TrueForAll(static value => value.ValueType is ClaimValueTypes.String) ||
// Note: a unique claim using the special JSON_ARRAY claim value type is allowed
// if the individual elements of the parsed JSON array are all string values.
(values is [{ ValueType: JsonClaimValueTypes.JsonArray, Value: string value }] &&
JsonSerializer.Deserialize(value, OpenIddictSerializer.Default.JsonElement)
is { ValueKind: JsonValueKind.Array } element &&
OpenIddictHelpers.ValidateArrayElements(element, JsonValueKind.String)),

// The following claims MUST be represented as unique strings.
Claims.AuthorizedParty or Claims.Issuer or Claims.JwtId or Claims.Subject
=> values is [{ ValueType: ClaimValueTypes.String }],

// The following claims MUST be represented as unique numeric dates.
Expand Down Expand Up @@ -951,18 +957,28 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
case OpenIddictServerEndpointType.Revocation when context.Options.AcceptAnonymousClients:
return;

// Note: the authorization code and client credentials grant types never
// allow anonymous clients, even if the corresponding option is enabled.
// Note: the authorization code, device code and client credentials grant types
// never allow anonymous clients, even if the corresponding option is enabled.
case OpenIddictServerEndpointType.Token when context.Options.AcceptAnonymousClients &&
!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsClientCredentialsGrantType():
!context.Request.IsClientCredentialsGrantType() &&
!context.Request.IsDeviceCodeGrantType():
return;

// Note: despite being conceptually similar to the token endpoint, the pushed authorization
// endpoint deliberately doesn't allow anonymous clients, as a client_id is always required
// for both regular authorization requests and pushed authorization requests.
//
// See https://datatracker.ietf.org/doc/html/rfc9126#section-2.1 for more information.

// Note: similarly, the device authorization endpoint doesn't allow anonymous clients,
// as a client_id parameter is always required by the specification if the client
// doesn't authenticate using a different method.
//
// See https://datatracker.ietf.org/doc/html/rfc9126#section-3.1 for more information.
case OpenIddictServerEndpointType.DeviceAuthorization:
case OpenIddictServerEndpointType.PushedAuthorization:
break;
}

context.Logger.LogInformation(6220, SR.GetResourceString(SR.ID6220), Parameters.ClientId);
Expand Down Expand Up @@ -1054,7 +1070,7 @@ OpenIddictServerEndpointType.EndUserVerification or
if (context.EndpointType is OpenIddictServerEndpointType.Token &&
context.Request.IsClientCredentialsGrantType())
{
context.Logger.LogInformation(6222, SR.GetResourceString(SR.ID6222), context.Request.ClientId);
context.Logger.LogInformation(6222, SR.GetResourceString(SR.ID6222), context.ClientId);

context.Reject(
error: Errors.UnauthorizedClient,
Expand Down Expand Up @@ -1633,7 +1649,7 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
// Presenter validation is disabled for the token endpoint as this endpoint
// Presenter validation is disabled for the authorization code grant as it
// implements a specialized event handler that uses more complex rules.
DisablePresenterValidation = context.EndpointType is OpenIddictServerEndpointType.Token &&
context.Request.IsAuthorizationCodeGrantType(),
Expand Down Expand Up @@ -1712,7 +1728,7 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
// Presenter validation is disabled for the token endpoint as this endpoint
// Presenter validation is disabled for the device code grant as it
// implements a specialized event handler that uses more complex rules.
DisablePresenterValidation = context.EndpointType is OpenIddictServerEndpointType.Token &&
context.Request.IsDeviceCodeGrantType(),
Expand Down Expand Up @@ -1973,7 +1989,7 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)
var notification = new ValidateTokenContext(context.Transaction)
{
DisableAudienceValidation = true,
// Presenter validation is disabled for the token endpoint as this endpoint
// Presenter validation is disabled for the refresh token grant as it
// implements a specialized event handler that uses more complex rules.
DisablePresenterValidation = context.EndpointType is OpenIddictServerEndpointType.Token &&
context.Request.IsRefreshTokenGrantType(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,12 +705,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default presenter validation.
if (context.ValidPresenters.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any presenter attached, return an error.
var presenters = context.Principal.GetPresenters();
if (presenters.IsDefaultOrEmpty)
Expand Down Expand Up @@ -765,12 +759,6 @@ public ValueTask HandleAsync(ValidateTokenContext context)

Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));

// If no specific value is expected, skip the default audience validation.
if (context.ValidAudiences.Count is 0)
{
return ValueTask.CompletedTask;
}

// If the token doesn't have any audience attached, return an error.
var audiences = context.Principal.GetAudiences();
if (audiences.IsDefaultOrEmpty)
Expand Down
6 changes: 4 additions & 2 deletions src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -977,12 +977,14 @@ public async ValueTask HandleAsync(ProcessAuthenticationContext context)

var notification = new ValidateTokenContext(context.Transaction)
{
// Note: by default, access tokens are not constrainted to specific presenters but must contain
// at least one audience matching one of the values configured in the options, if applicable.
DisableAudienceValidation = context.Options.Audiences.Count is 0,
DisablePresenterValidation = true,
Token = context.AccessToken,
ValidTokenTypes = { TokenTypeIdentifiers.AccessToken }
};

// Note: by default, access tokens are not constrainted to specific presenters but must contain
// at least one audience matching one of the values configured in the options, if applicable.
notification.ValidAudiences.UnionWith(context.Options.Audiences);

await _dispatcher.DispatchAsync(notification);
Expand Down
Loading
Loading