Skip to content

Commit

Permalink
Update the VK ID provider to support attaching the device identifier …
Browse files Browse the repository at this point in the history
…to all token requests
  • Loading branch information
kevinchalet committed Feb 10, 2025
1 parent bb0b75c commit f937100
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 15 deletions.
3 changes: 3 additions & 0 deletions src/OpenIddict.Abstractions/OpenIddictResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,9 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
<data name="ID0459" xml:space="preserve">
<value>A token must be specified when using revocation.</value>
</data>
<data name="ID0467" xml:space="preserve">
<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>
</data>
<data name="ID2000" xml:space="preserve">
<value>The security token is missing.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,8 @@ public ValueTask HandleAsync(ExtractUserInfoResponseContext context)
ProviderTypes.ExactOnline => new(context.Response["d"]?["results"]?[0]?.GetNamedParameters() ??
throw new InvalidOperationException(SR.FormatID0334("d/results/0"))),

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,25 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
}
}

// VK ID uses a non-standard "device_id" parameter in authorization responses.
else if (context.Registration.ProviderType is ProviderTypes.VkId)
{
var identifier = (string?) context.Request["device_id"];
if (string.IsNullOrEmpty(identifier))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2029("device_id"),
uri: SR.FormatID8000(SR.ID2029));

return default;
}

// Store the device identifier as an authentication property
// so it can be resolved later to make refresh token requests.
context.Properties[VkId.Properties.DeviceId] = identifier;
}

// Zoho returns the region of the authenticated user as a non-standard "location" parameter
// that must be used to compute the address of the token and userinfo endpoints.
else if (context.Registration.ProviderType is ProviderTypes.Zoho)
Expand All @@ -407,7 +426,7 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
}

// Ensure the specified location corresponds to well-known region.
if (location.ToUpperInvariant() is not ( "AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
if (location.ToUpperInvariant() is not ("AU" or "CA" or "EU" or "IN" or "JP" or "SA" or "US"))
{
context.Reject(
error: Errors.InvalidRequest,
Expand Down Expand Up @@ -640,11 +659,18 @@ public ValueTask HandleAsync(ProcessAuthenticationContext context)
context.TokenRequest.UserCode = code;
}

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

context.TokenRequest["device_id"] = identifier;
}

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

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

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

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

// These providers return the username as a custom "display_name" node:
ProviderTypes.Spotify or ProviderTypes.StackExchange or ProviderTypes.Yandex or
ProviderTypes.Zoom
ProviderTypes.Spotify or ProviderTypes.StackExchange or
ProviderTypes.Yandex or ProviderTypes.Zoom
=> (string?) context.UserInfoResponse?["display_name"],

// Strava returns the username as a custom "athlete/username" node in token responses:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2080,10 +2080,13 @@
TokenEndpoint="https://id.vk.com/oauth2/auth"
UserInfoEndpoint="https://id.vk.com/oauth2/user_info">
<CodeChallengeMethod Value="S256" />

<GrantType Value="authorization_code" />
<GrantType Value="refresh_token" />
</Configuration>
</Environment>

<Property Name="DeviceId" DictionaryKey=".device_id" />
</Provider>

<!--
Expand Down Expand Up @@ -2224,14 +2227,12 @@
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-->

<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1"
Documentation="https://yandex.ru/dev/id/doc/en/">
<Provider Name="Yandex" Id="313298d4-d210-4541-a348-96ced013dab1" Documentation="https://yandex.ru/dev/id/doc/en/">
<Environment Issuer="https://oauth.yandex.ru/">
<Configuration AuthorizationEndpoint="https://oauth.yandex.ru/authorize"
RevokationEndpoint="https://oauth.yandex.ru/revoke_token"
RevocationEndpoint="https://oauth.yandex.ru/revoke_token"
TokenEndpoint="https://oauth.yandex.ru/token"
UserInfoEndpoint="https://login.yandex.ru/info">

<GrantType Value="authorization_code" />
<GrantType Value="refresh_token" />
</Configuration>
Expand Down

0 comments on commit f937100

Please sign in to comment.