Skip to content

Commit 03bd575

Browse files
authored
Add Yandex and VK ID to the list of supported providers
1 parent bd74a6c commit 03bd575

File tree

5 files changed

+180
-26
lines changed

5 files changed

+180
-26
lines changed

src/OpenIddict.Abstractions/OpenIddictResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,6 +1707,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
17071707
<data name="ID0459" xml:space="preserve">
17081708
<value>A token must be specified when using revocation.</value>
17091709
</data>
1710+
<data name="ID0467" xml:space="preserve">
1711+
<value>The VK ID integration requires sending the device identifier to the token and revocation endpoints. For that, attach a ".device_id" authentication property containing the device identifier returned by the authorization endpoint.</value>
1712+
</data>
17101713
<data name="ID2000" xml:space="preserve">
17111714
<value>The security token is missing.</value>
17121715
</data>

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Revocation.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public static class Revocation
2121
/*
2222
* Revocation request preparation:
2323
*/
24+
MapNonStandardRequestParameters.Descriptor,
2425
OverrideHttpMethod.Descriptor,
2526
AttachBearerAccessToken.Descriptor,
2627

@@ -30,6 +31,43 @@ public static class Revocation
3031
NormalizeContentType.Descriptor
3132
]);
3233

34+
/// <summary>
35+
/// Contains the logic responsible for mapping non-standard request parameters
36+
/// to their standard equivalent for the providers that require it.
37+
/// </summary>
38+
public sealed class MapNonStandardRequestParameters : IOpenIddictClientHandler<PrepareRevocationRequestContext>
39+
{
40+
/// <summary>
41+
/// Gets the default descriptor definition assigned to this handler.
42+
/// </summary>
43+
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
44+
= OpenIddictClientHandlerDescriptor.CreateBuilder<PrepareRevocationRequestContext>()
45+
.UseSingletonHandler<MapNonStandardRequestParameters>()
46+
.SetOrder(int.MinValue + 100_000)
47+
.SetType(OpenIddictClientHandlerType.BuiltIn)
48+
.Build();
49+
50+
/// <inheritdoc/>
51+
public ValueTask HandleAsync(PrepareRevocationRequestContext context)
52+
{
53+
if (context is null)
54+
{
55+
throw new ArgumentNullException(nameof(context));
56+
}
57+
58+
// Weibo, VK ID and Yandex don't support the standard "token" parameter and
59+
// require using the non-standard "access_token" parameter instead.
60+
if (context.Registration.ProviderType is ProviderTypes.Weibo or ProviderTypes.VkId or ProviderTypes.Yandex)
61+
{
62+
context.Request.AccessToken = context.Token;
63+
context.Request.Token = null;
64+
context.Request.TokenTypeHint = null;
65+
}
66+
67+
return default;
68+
}
69+
}
70+
3371
/// <summary>
3472
/// Contains the logic responsible for overriding the HTTP method for the providers that require it.
3573
/// </summary>
@@ -61,7 +99,6 @@ public ValueTask HandleAsync(PrepareRevocationRequestContext context)
6199

62100
request.Method = context.Registration.ProviderType switch
63101
{
64-
65102
ProviderTypes.Zendesk => HttpMethod.Delete,
66103

67104
_ => request.Method

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.Userinfo.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ public ValueTask HandleAsync(PrepareUserInfoRequestContext context)
236236
context.Request["f"] = "json";
237237
}
238238

239+
// VK ID requires attaching the "client_id" parameter to userinfo requests.
240+
else if (context.Registration.ProviderType is ProviderTypes.VkId)
241+
{
242+
context.Request.ClientId = context.Registration.ClientId;
243+
}
244+
239245
return default;
240246
}
241247
}
@@ -406,8 +412,8 @@ public ValueTask HandleAsync(ExtractUserInfoResponseContext context)
406412
ProviderTypes.ExactOnline => new(context.Response["d"]?["results"]?[0]?.GetNamedParameters() ??
407413
throw new InvalidOperationException(SR.FormatID0334("d/results/0"))),
408414

409-
// Fitbit, Todoist and Zendesk return a nested "user" object.
410-
ProviderTypes.Fitbit or ProviderTypes.Todoist or ProviderTypes.Zendesk
415+
// These providers return a nested "user" object.
416+
ProviderTypes.Fitbit or ProviderTypes.Todoist or ProviderTypes.VkId or ProviderTypes.Zendesk
411417
=> new(context.Response["user"]?.GetNamedParameters() ??
412418
throw new InvalidOperationException(SR.FormatID0334("user"))),
413419

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,25 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
391391
}
392392
}
393393

394+
// VK ID uses a non-standard "device_id" parameter in authorization responses.
395+
else if (context.Registration.ProviderType is ProviderTypes.VkId)
396+
{
397+
var identifier = (string?) context.Request["device_id"];
398+
if (string.IsNullOrEmpty(identifier))
399+
{
400+
context.Reject(
401+
error: Errors.InvalidRequest,
402+
description: SR.FormatID2029("device_id"),
403+
uri: SR.FormatID8000(SR.ID2029));
404+
405+
return default;
406+
}
407+
408+
// Store the device identifier as an authentication property
409+
// so it can be resolved later to make refresh token requests.
410+
context.Properties[VkId.Properties.DeviceId] = identifier;
411+
}
412+
394413
// Zoho returns the region of the authenticated user as a non-standard "location" parameter
395414
// that must be used to compute the address of the token and userinfo endpoints.
396415
else if (context.Registration.ProviderType is ProviderTypes.Zoho)
@@ -407,7 +426,7 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
407426
}
408427

409428
// Ensure the specified location corresponds to well-known region.
410-
if (location.ToUpperInvariant() is not ( "AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
429+
if (location.ToUpperInvariant() is not ("AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
411430
{
412431
context.Reject(
413432
error: Errors.InvalidRequest,
@@ -640,6 +659,23 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
640659
context.TokenRequest.UserCode = code;
641660
}
642661

662+
// VK ID requires attaching a non-standard "device_id" parameter to all token requests.
663+
//
664+
// This parameter is either resolved from the authorization response (for the authorization
665+
// code or hybrid grants) or manually provided by the application for other grant types.
666+
else if (context.Registration.ProviderType is ProviderTypes.VkId)
667+
{
668+
context.TokenRequest["device_id"] = context.GrantType switch
669+
{
670+
GrantTypes.AuthorizationCode or GrantTypes.Implicit => context.Request["device_id"],
671+
672+
_ when context.Properties.TryGetValue(VkId.Properties.DeviceId, out string? identifier) &&
673+
!string.IsNullOrEmpty(identifier) => identifier,
674+
675+
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0467))
676+
};
677+
}
678+
643679
return default;
644680
}
645681
}
@@ -1363,6 +1399,9 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
13631399
// Shopify returns the email address as a custom "associated_user/email" node in token responses:
13641400
ProviderTypes.Shopify => (string?) context.TokenResponse?["associated_user"]?["email"],
13651401

1402+
// Yandex returns the email address as a custom "default_email" node:
1403+
ProviderTypes.Yandex => (string?) context.UserInfoResponse?["default_email"],
1404+
13661405
_ => context.MergedPrincipal.GetClaim(ClaimTypes.Email)
13671406
});
13681407

@@ -1375,8 +1414,8 @@ ProviderTypes.Lichess or ProviderTypes.Mastodon or ProviderTypes.Mixclou
13751414
ProviderTypes.Trakt or ProviderTypes.WordPress
13761415
=> (string?) context.UserInfoResponse?["username"],
13771416

1378-
// Basecamp and Harvest don't return a username so one is created using the "first_name" and "last_name" nodes:
1379-
ProviderTypes.Basecamp or ProviderTypes.Harvest
1417+
// These providers don't return a username so one is created using the "first_name" and "last_name" nodes:
1418+
ProviderTypes.Basecamp or ProviderTypes.Harvest or ProviderTypes.VkId
13801419
when context.UserInfoResponse?.HasParameter("first_name") is true &&
13811420
context.UserInfoResponse?.HasParameter("last_name") is true
13821421
=> $"{(string?) context.UserInfoResponse?["first_name"]} {(string?) context.UserInfoResponse?["last_name"]}",
@@ -1423,7 +1462,8 @@ when context.TokenResponse?["associated_user"]?["first_name"] is not null &&
14231462
=> $"{(string?) context.UserInfoResponse?["firstName"]} {(string?) context.UserInfoResponse?["lastName"]}",
14241463

14251464
// These providers return the username as a custom "display_name" node:
1426-
ProviderTypes.Spotify or ProviderTypes.StackExchange or ProviderTypes.Zoom
1465+
ProviderTypes.Spotify or ProviderTypes.StackExchange or
1466+
ProviderTypes.Yandex or ProviderTypes.Zoom
14271467
=> (string?) context.UserInfoResponse?["display_name"],
14281468

14291469
// Strava returns the username as a custom "athlete/username" node in token responses:
@@ -1451,7 +1491,8 @@ ProviderTypes.Spotify or ProviderTypes.StackExchange or ProviderTypes.Zoom
14511491
{
14521492
// These providers return the user identifier as a custom "user_id" node:
14531493
ProviderTypes.Amazon or ProviderTypes.HubSpot or
1454-
ProviderTypes.StackExchange or ProviderTypes.Typeform
1494+
ProviderTypes.StackExchange or ProviderTypes.Typeform or
1495+
ProviderTypes.VkId
14551496
=> (string?) context.UserInfoResponse?["user_id"],
14561497

14571498
// ArcGIS and Trakt don't return a user identifier and require using the username as the identifier:
@@ -1462,16 +1503,16 @@ ProviderTypes.ArcGisOnline or ProviderTypes.Trakt
14621503
ProviderTypes.Atlassian => (string?) context.UserInfoResponse?["account_id"],
14631504

14641505
// These providers return the user identifier as a custom "id" node:
1465-
ProviderTypes.Airtable or ProviderTypes.Basecamp or ProviderTypes.Box or
1466-
ProviderTypes.Dailymotion or ProviderTypes.Deezer or ProviderTypes.Discord or
1467-
ProviderTypes.Disqus or ProviderTypes.Facebook or ProviderTypes.GitCode or
1468-
ProviderTypes.Gitee or ProviderTypes.GitHub or ProviderTypes.Harvest or
1469-
ProviderTypes.Kook or ProviderTypes.Kroger or ProviderTypes.Lichess or
1470-
ProviderTypes.Mastodon or ProviderTypes.Meetup or ProviderTypes.Nextcloud or
1471-
ProviderTypes.Patreon or ProviderTypes.Pipedrive or ProviderTypes.Reddit or
1472-
ProviderTypes.Smartsheet or ProviderTypes.Spotify or ProviderTypes.SubscribeStar or
1473-
ProviderTypes.Todoist or ProviderTypes.Twitter or ProviderTypes.Weibo or
1474-
ProviderTypes.Zoom
1506+
ProviderTypes.Airtable or ProviderTypes.Basecamp or ProviderTypes.Box or
1507+
ProviderTypes.Dailymotion or ProviderTypes.Deezer or ProviderTypes.Discord or
1508+
ProviderTypes.Disqus or ProviderTypes.Facebook or ProviderTypes.Gitee or
1509+
ProviderTypes.GitHub or ProviderTypes.Harvest or ProviderTypes.Kook or
1510+
ProviderTypes.Kroger or ProviderTypes.Lichess or ProviderTypes.Mastodon or
1511+
ProviderTypes.Meetup or ProviderTypes.Nextcloud or ProviderTypes.Patreon or
1512+
ProviderTypes.Pipedrive or ProviderTypes.Reddit or ProviderTypes.Smartsheet or
1513+
ProviderTypes.Spotify or ProviderTypes.SubscribeStar or ProviderTypes.Todoist or
1514+
ProviderTypes.Twitter or ProviderTypes.Weibo or ProviderTypes.Yandex or
1515+
ProviderTypes.Zoom
14751516
=> (string?) context.UserInfoResponse?["id"],
14761517

14771518
// Bitbucket returns the user identifier as a custom "uuid" node:
@@ -1920,6 +1961,27 @@ public ValueTask HandleAsync(ProcessChallengeContext context)
19201961
context.Request["language"] = settings.Language;
19211962
}
19221963

1964+
// Yandex allows sending optional "device_id" and "device_name" parameters.
1965+
else if (context.Registration.ProviderType is ProviderTypes.Yandex)
1966+
{
1967+
var settings = context.Registration.GetYandexSettings();
1968+
1969+
if (!context.Properties.TryGetValue(Yandex.Properties.DeviceId, out string? identifier) ||
1970+
string.IsNullOrEmpty(identifier))
1971+
{
1972+
identifier = settings.DeviceId;
1973+
}
1974+
1975+
if (!context.Properties.TryGetValue(Yandex.Properties.DeviceName, out string? name) ||
1976+
string.IsNullOrEmpty(name))
1977+
{
1978+
name = settings.DeviceName;
1979+
}
1980+
1981+
context.Request["device_id"] = identifier;
1982+
context.Request["device_name"] = name;
1983+
}
1984+
19231985
// By default, Zoho doesn't return a refresh token but
19241986
// allows sending an "access_type" parameter to retrieve one.
19251987
else if (context.Registration.ProviderType is ProviderTypes.Zoho)
@@ -2058,14 +2120,6 @@ public ValueTask HandleAsync(ProcessRevocationContext context)
20582120
context.RevocationRequest.ClientAssertionType = null;
20592121
}
20602122

2061-
// Weibo implements a non-standard client authentication method for its endpoints that
2062-
// requires sending the token as "access_token" instead of the standard "token" parameter.
2063-
else if (context.Registration.ProviderType is ProviderTypes.Weibo)
2064-
{
2065-
context.RevocationRequest.AccessToken = context.RevocationRequest.Token;
2066-
context.RevocationRequest.Token = null;
2067-
}
2068-
20692123
return default;
20702124
}
20712125
}

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,6 +2065,31 @@
20652065
</Environment>
20662066
</Provider>
20672067

2068+
<!--
2069+
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
2070+
██ ███ █ █▀▄████▄ ▄██ ▄▄▀██
2071+
███ █ ██ ▄▀██████ ███ ██ ██
2072+
███▄▀▄██ ██ ████▀ ▀██ ▀▀ ██
2073+
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
2074+
-->
2075+
2076+
<Provider Name="VkId" Id="9bf89c19-401b-4076-893e-a4136e719432"
2077+
Documentation="https://id.vk.com/about/business/go/docs/en/vkid/latest/oauth-vk">
2078+
<Environment Issuer="https://id.vk.com/">
2079+
<Configuration AuthorizationEndpoint="https://id.vk.com/authorize"
2080+
RevocationEndpoint="https://id.vk.com/oauth2/revoke"
2081+
TokenEndpoint="https://id.vk.com/oauth2/auth"
2082+
UserInfoEndpoint="https://id.vk.com/oauth2/user_info">
2083+
<CodeChallengeMethod Value="S256" />
2084+
2085+
<GrantType Value="authorization_code" />
2086+
<GrantType Value="refresh_token" />
2087+
</Configuration>
2088+
</Environment>
2089+
2090+
<Property Name="DeviceId" DictionaryKey=".device_id" />
2091+
</Provider>
2092+
20682093
<!--
20692094
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
20702095
██ ███ ██ ▄▄▄██ ▄▄▀██ ▄▄▄█▄▀█▀▄██
@@ -2193,6 +2218,35 @@
21932218
<Provider Name="Yahoo" Id="874d78ec-3d79-4492-ab79-76a7dd7fa0b5"
21942219
Documentation="https://developer.yahoo.com/oauth2/guide/openid_connect/">
21952220
<Environment Issuer="https://api.login.yahoo.com/" />
2221+
</Provider>
2222+
2223+
<!--
2224+
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
2225+
██ ███ █ ▄▄▀██ ▀██ ██ ▄▄▀██ ▄▄▄██▄▀█▀▄██
2226+
██▄▀▀▀▄█ ▀▀ ██ █ █ ██ ██ ██ ▄▄▄████ ████
2227+
████ ███ ██ ██ ██▄ ██ ▀▀ ██ ▀▀▀██▀▄█▄▀██
2228+
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
2229+
-->
2230+
2231+
<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1" Documentation="https://yandex.ru/dev/id/doc/en/">
2232+
<Environment Issuer="https://oauth.yandex.ru/">
2233+
<Configuration AuthorizationEndpoint="https://oauth.yandex.ru/authorize"
2234+
RevocationEndpoint="https://oauth.yandex.ru/revoke_token"
2235+
TokenEndpoint="https://oauth.yandex.ru/token"
2236+
UserInfoEndpoint="https://login.yandex.ru/info">
2237+
<GrantType Value="authorization_code" />
2238+
<GrantType Value="refresh_token" />
2239+
</Configuration>
2240+
</Environment>
2241+
2242+
<Property Name="DeviceId" DictionaryKey=".device_id" />
2243+
<Property Name="DeviceName" DictionaryKey=".device_name" />
2244+
2245+
<Setting PropertyName="DeviceId" ParameterName="identifier" Type="String" Required="false"
2246+
Description="Gets or sets the optional device identifier that will be attached to authorization requests" />
2247+
2248+
<Setting PropertyName="DeviceName" ParameterName="name" Type="String" Required="false"
2249+
Description="Gets or sets the optional device name that will be attached to authorization requests" />
21962250
</Provider>
21972251

21982252
<!--

0 commit comments

Comments
 (0)