Skip to content

Commit 8278845

Browse files
Update to Instagram Basic Display API
Update the Instagram provider to use the Basic Display API. See #441.
1 parent 800255d commit 8278845

File tree

7 files changed

+111
-49
lines changed

7 files changed

+111
-49
lines changed

docs/instagram.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@ A Facebook authentication provider is [available from Microsoft](https://docs.mi
1414
services.AddAuthentication(options => /* Auth configuration */)
1515
.AddInstagram(options =>
1616
{
17-
options.ClientId = "my-client-id";
18-
options.ClientSecret = "my-client-secret";
19-
});
17+
options.ClientId = Configuration["Instagram:ClientId"];
18+
options.ClientSecret = Configuration["Instagram:ClientSecret"];
19+
20+
// Optionally return the account type
21+
options.Fields.Add("account_type");
22+
23+
// Optionally return the user's media
24+
options.Fields.Add("media_count");
25+
options.Fields.Add("media");
26+
options.Scope.Add("user_media");
27+
})
2028
```
2129

2230
## Required Additional Settings
@@ -27,4 +35,4 @@ _None._
2735

2836
| Property Name | Property Type | Description | Default Value |
2937
|:--|:--|:--|:--|
30-
| `UseSignedRequests` | `bool` | Indicates whether requests to the Instagram API's user information endpoint should be signed. | `false` |
38+
| `Fields` | `ISet<string>` | The list of list of fields and edges to retrieve from the user information endpoint. | `[ "id", "username" ]` |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
namespace AspNet.Security.OAuth.Instagram
8+
{
9+
/// <summary>
10+
/// Contains constants specific to the <see cref="InstagramAuthenticationHandler"/>.
11+
/// </summary>
12+
public static class InstagramAuthenticationConstants
13+
{
14+
/// <summary>
15+
/// Contains constants for claims.
16+
/// </summary>
17+
public static class Claims
18+
{
19+
/// <summary>
20+
/// The claim for the user's account type.
21+
/// </summary>
22+
public const string AccountType = "urn:instagram:accounttype";
23+
24+
/// <summary>
25+
/// The claim for the user's media count.
26+
/// </summary>
27+
public const string MediaCount = "urn:instagram:mediacount";
28+
}
29+
}
30+
}

src/AspNet.Security.OAuth.Instagram/InstagramAuthenticationDefaults.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ public static class InstagramAuthenticationDefaults
4747
/// <summary>
4848
/// Default value for <see cref="OAuthOptions.UserInformationEndpoint"/>.
4949
/// </summary>
50-
public const string UserInformationEndpoint = "https://api.instagram.com/v1/users/self";
50+
public const string UserInformationEndpoint = "https://graph.instagram.com/me";
5151
}
5252
}

src/AspNet.Security.OAuth.Instagram/InstagramAuthenticationHandler.cs

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55
*/
66

77
using System;
8-
using System.Diagnostics;
9-
using System.Linq;
108
using System.Net.Http;
119
using System.Net.Http.Headers;
1210
using System.Security.Claims;
13-
using System.Security.Cryptography;
14-
using System.Text;
1511
using System.Text.Encodings.Web;
1612
using System.Text.Json;
1713
using System.Threading.Tasks;
@@ -42,13 +38,9 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
4238
{
4339
string address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, "access_token", tokens.AccessToken);
4440

45-
if (Options.UseSignedRequests)
41+
if (Options.Fields.Count > 0)
4642
{
47-
// Compute the HMAC256 signature.
48-
string signature = ComputeSignature(address);
49-
50-
// Add the signature to the query string.
51-
address = QueryHelpers.AddQueryString(address, "sig", signature);
43+
address = QueryHelpers.AddQueryString(address, "fields", string.Join(",", Options.Fields));
5244
}
5345

5446
using var request = new HttpRequestMessage(HttpMethod.Get, address);
@@ -70,37 +62,16 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
7062

7163
var principal = new ClaimsPrincipal(identity);
7264
var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
73-
context.RunClaimActions(payload.RootElement.GetProperty("data"));
65+
context.RunClaimActions();
7466

7567
await Options.Events.CreatingTicket(context);
7668
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name);
7769
}
7870

71+
[Obsolete("This method is no longer called and will be removed in a future version.")]
7972
protected virtual string ComputeSignature([NotNull] string address)
8073
{
81-
string query = new UriBuilder(address).Query;
82-
83-
// Extract the parameters from the query string.
84-
string[] parameters =
85-
(from parameter in QueryHelpers.ParseQuery(query)
86-
orderby parameter.Key
87-
select $"{parameter.Key}={parameter.Value}").ToArray();
88-
89-
Debug.Assert(parameters.Length < 1, "No parameters found in query string.");
90-
91-
// See https://www.instagram.com/developer/secure-api-requests/
92-
// for more information about the signature format.
93-
byte[] bytes = Encoding.UTF8.GetBytes($"/users/self|{string.Join("|", parameters)}");
94-
95-
// Compute the HMAC256 signature.
96-
byte[] key = Encoding.UTF8.GetBytes(Options.ClientSecret);
97-
98-
using var algorithm = new HMACSHA256(key);
99-
100-
byte[] hash = algorithm.ComputeHash(bytes);
101-
102-
// Convert the hash to its lowercased hexadecimal representation.
103-
return BitConverter.ToString(hash).Replace("-", string.Empty, StringComparison.Ordinal).ToLowerInvariant();
74+
return string.Empty;
10475
}
10576
}
10677
}

src/AspNet.Security.OAuth.Instagram/InstagramAuthenticationOptions.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
* for more information concerning the license and the contributors participating to this project.
55
*/
66

7+
using System;
8+
using System.Collections.Generic;
79
using System.Security.Claims;
810
using Microsoft.AspNetCore.Authentication;
911
using Microsoft.AspNetCore.Authentication.OAuth;
12+
using static AspNet.Security.OAuth.Instagram.InstagramAuthenticationConstants;
1013

1114
namespace AspNet.Security.OAuth.Instagram
1215
{
@@ -18,17 +21,18 @@ public class InstagramAuthenticationOptions : OAuthOptions
1821
public InstagramAuthenticationOptions()
1922
{
2023
ClaimsIssuer = InstagramAuthenticationDefaults.Issuer;
21-
2224
CallbackPath = InstagramAuthenticationDefaults.CallbackPath;
2325

2426
AuthorizationEndpoint = InstagramAuthenticationDefaults.AuthorizationEndpoint;
2527
TokenEndpoint = InstagramAuthenticationDefaults.TokenEndpoint;
2628
UserInformationEndpoint = InstagramAuthenticationDefaults.UserInformationEndpoint;
2729

28-
Scope.Add("basic");
30+
Scope.Add("user_profile");
2931

3032
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id");
31-
ClaimActions.MapJsonKey(ClaimTypes.Name, "full_name");
33+
ClaimActions.MapJsonKey(ClaimTypes.Name, "username");
34+
ClaimActions.MapJsonKey(Claims.AccountType, "account_type");
35+
ClaimActions.MapJsonKey(Claims.MediaCount, "media_count");
3236
}
3337

3438
/// <summary>
@@ -38,6 +42,16 @@ public InstagramAuthenticationOptions()
3842
/// Enabling this option is recommended when the client application
3943
/// has been configured to enforce signed requests.
4044
/// </summary>
45+
[Obsolete("This property no longer has any effect and will be removed in a future version.")]
4146
public bool UseSignedRequests { get; set; }
47+
48+
/// <summary>
49+
/// Gets the list of list of fields and edges to retrieve from the user information endpoint.
50+
/// </summary>
51+
public ISet<string> Fields { get; } = new HashSet<string>()
52+
{
53+
"id",
54+
"username",
55+
};
4256
}
4357
}

test/AspNet.Security.OAuth.Providers.Tests/Instagram/InstagramTests.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.Extensions.DependencyInjection;
1111
using Xunit;
1212
using Xunit.Abstractions;
13+
using static AspNet.Security.OAuth.Instagram.InstagramAuthenticationConstants;
1314

1415
namespace AspNet.Security.OAuth.Instagram
1516
{
@@ -28,8 +29,8 @@ protected internal override void RegisterAuthentication(AuthenticationBuilder bu
2829
}
2930

3031
[Theory]
31-
[InlineData(ClaimTypes.NameIdentifier, "my-id")]
32-
[InlineData(ClaimTypes.Name, "John Smith")]
32+
[InlineData(ClaimTypes.Name, "jayposiris")]
33+
[InlineData(ClaimTypes.NameIdentifier, "17841405793187218")]
3334
public async Task Can_Sign_In_Using_Instagram(string claimType, string claimValue)
3435
{
3536
// Arrange
@@ -41,5 +42,32 @@ public async Task Can_Sign_In_Using_Instagram(string claimType, string claimValu
4142
// Assert
4243
AssertClaim(claims, claimType, claimValue);
4344
}
45+
46+
[Theory]
47+
[InlineData(ClaimTypes.Name, "jayposiris")]
48+
[InlineData(ClaimTypes.NameIdentifier, "17841405793187218")]
49+
[InlineData(Claims.AccountType, "PERSONAL")]
50+
[InlineData(Claims.MediaCount, "42")]
51+
public async Task Can_Sign_In_Using_Instagram_With_Additional_Fields(string claimType, string claimValue)
52+
{
53+
// Arrange
54+
static void ConfigureServices(IServiceCollection services)
55+
{
56+
services.PostConfigureAll<InstagramAuthenticationOptions>((options) =>
57+
{
58+
options.Fields.Add("account_type");
59+
options.Fields.Add("media_count");
60+
options.Scope.Add("user_media");
61+
});
62+
}
63+
64+
using var server = CreateTestServer(ConfigureServices);
65+
66+
// Act
67+
var claims = await AuthenticateUserAsync(server);
68+
69+
// Assert
70+
AssertClaim(claims, claimType, claimValue);
71+
}
4472
}
4573
}

test/AspNet.Security.OAuth.Providers.Tests/Instagram/bundle.json

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
33
"items": [
44
{
5+
"comment": "See https://developers.facebook.com/docs/instagram-basic-display-api/getting-started#step-5--exchange-the-code-for-a-token",
56
"uri": "https://api.instagram.com/oauth/access_token",
67
"method": "POST",
78
"contentFormat": "json",
@@ -13,13 +14,23 @@
1314
}
1415
},
1516
{
16-
"uri": "https://api.instagram.com/v1/users/self?access_token=secret-access-token",
17+
"comment": "See https://developers.facebook.com/docs/instagram-basic-display-api/reference/user#user",
18+
"uri": "https://graph.instagram.com/me?access_token=secret-access-token&fields=id,username",
1719
"contentFormat": "json",
1820
"contentJson": {
19-
"data": {
20-
"id": "my-id",
21-
"full_name": "John Smith"
22-
}
21+
"id": "17841405793187218",
22+
"username": "jayposiris"
23+
}
24+
},
25+
{
26+
"comment": "See https://developers.facebook.com/docs/instagram-basic-display-api/reference/user#user",
27+
"uri": "https://graph.instagram.com/me?access_token=secret-access-token&fields=id,username,account_type,media_count",
28+
"contentFormat": "json",
29+
"contentJson": {
30+
"id": "17841405793187218",
31+
"username": "jayposiris",
32+
"account_type": "PERSONAL",
33+
"media_count": 42
2334
}
2435
}
2536
]

0 commit comments

Comments
 (0)