Skip to content

Commit f937100

Browse files
committed
Update the VK ID provider to support attaching the device identifier to all token requests
1 parent bb0b75c commit f937100

File tree

4 files changed

+43
-15
lines changed

4 files changed

+43
-15
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 endpoint. 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.Userinfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,8 +412,8 @@ public ValueTask HandleAsync(ExtractUserInfoResponseContext context)
412412
ProviderTypes.ExactOnline => new(context.Response["d"]?["results"]?[0]?.GetNamedParameters() ??
413413
throw new InvalidOperationException(SR.FormatID0334("d/results/0"))),
414414

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

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

Lines changed: 33 additions & 9 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,11 +659,18 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
640659
context.TokenRequest.UserCode = code;
641660
}
642661

643-
// VK ID requires flowing the non-standard "device_id" parameter
644-
// from authorization responses to token requests.
662+
// VK ID requires attaching a non-standard "device_id" parameter to all token requests.
663+
// This parameter is either resolved from the authorization response (for the authorization
664+
// code or hybrid grants) or manually provided by the application for other grant types.
645665
else if (context.Registration.ProviderType is ProviderTypes.VkId)
646666
{
647-
context.TokenRequest["device_id"] = context.Request?["device_id"];
667+
if (!context.Properties.TryGetValue(VkId.Properties.DeviceId, out string? identifier) ||
668+
string.IsNullOrEmpty(identifier))
669+
{
670+
throw new InvalidOperationException(SR.GetResourceString(SR.ID0467));
671+
}
672+
673+
context.TokenRequest["device_id"] = identifier;
648674
}
649675

650676
return default;
@@ -1371,8 +1397,6 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
13711397
ProviderTypes.Shopify => (string?) context.TokenResponse?["associated_user"]?["email"],
13721398

13731399
// Yandex returns the email address as a custom "default_email" node:
1374-
// Note: email node is not a part of standard basic scope
1375-
// (https://yandex.ru/dev/id/doc/en/user-information#common)
13761400
ProviderTypes.Yandex => (string?) context.UserInfoResponse?["default_email"],
13771401

13781402
_ => context.MergedPrincipal.GetClaim(ClaimTypes.Email)
@@ -1387,7 +1411,7 @@ ProviderTypes.Lichess or ProviderTypes.Mastodon or ProviderTypes.Mixclou
13871411
ProviderTypes.Trakt or ProviderTypes.WordPress
13881412
=> (string?) context.UserInfoResponse?["username"],
13891413

1390-
// Basecamp and Harvest don't return a username so one is created using the "first_name" and "last_name" nodes:
1414+
// These providers don't return a username so one is created using the "first_name" and "last_name" nodes:
13911415
ProviderTypes.Basecamp or ProviderTypes.Harvest or ProviderTypes.VkId
13921416
when context.UserInfoResponse?.HasParameter("first_name") is true &&
13931417
context.UserInfoResponse?.HasParameter("last_name") is true
@@ -1435,8 +1459,8 @@ when context.TokenResponse?["associated_user"]?["first_name"] is not null &&
14351459
=> $"{(string?) context.UserInfoResponse?["firstName"]} {(string?) context.UserInfoResponse?["lastName"]}",
14361460

14371461
// These providers return the username as a custom "display_name" node:
1438-
ProviderTypes.Spotify or ProviderTypes.StackExchange or ProviderTypes.Yandex or
1439-
ProviderTypes.Zoom
1462+
ProviderTypes.Spotify or ProviderTypes.StackExchange or
1463+
ProviderTypes.Yandex or ProviderTypes.Zoom
14401464
=> (string?) context.UserInfoResponse?["display_name"],
14411465

14421466
// Strava returns the username as a custom "athlete/username" node in token responses:

src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationProviders.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2080,10 +2080,13 @@
20802080
TokenEndpoint="https://id.vk.com/oauth2/auth"
20812081
UserInfoEndpoint="https://id.vk.com/oauth2/user_info">
20822082
<CodeChallengeMethod Value="S256" />
2083+
20832084
<GrantType Value="authorization_code" />
20842085
<GrantType Value="refresh_token" />
20852086
</Configuration>
20862087
</Environment>
2088+
2089+
<Property Name="DeviceId" DictionaryKey=".device_id" />
20872090
</Provider>
20882091

20892092
<!--
@@ -2224,14 +2227,12 @@
22242227
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
22252228
-->
22262229

2227-
<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1"
2228-
Documentation="https://yandex.ru/dev/id/doc/en/">
2230+
<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1" Documentation="https://yandex.ru/dev/id/doc/en/">
22292231
<Environment Issuer="https://oauth.yandex.ru/">
22302232
<Configuration AuthorizationEndpoint="https://oauth.yandex.ru/authorize"
2231-
RevokationEndpoint="https://oauth.yandex.ru/revoke_token"
2233+
RevocationEndpoint="https://oauth.yandex.ru/revoke_token"
22322234
TokenEndpoint="https://oauth.yandex.ru/token"
22332235
UserInfoEndpoint="https://login.yandex.ru/info">
2234-
22352236
<GrantType Value="authorization_code" />
22362237
<GrantType Value="refresh_token" />
22372238
</Configuration>

0 commit comments

Comments
 (0)