@@ -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 }
0 commit comments