Skip to content

Commit 84bf2a0

Browse files
committed
chore: Update Etsy.md TOC
1 parent e662a3e commit 84bf2a0

File tree

1 file changed

+75
-48
lines changed

1 file changed

+75
-48
lines changed

docs/etsy.md

Lines changed: 75 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ Etsy's OAuth implementation uses Authorization Code with PKCE and issues refresh
1717
- [Manually Added Claims](#manually-added-claims)
1818
- [Advanced Configuration](#advanced-configuration)
1919
- [Accessing claims (Minimal API Sample)](#accessing-claims-minimal-api-sample)
20-
- [Directly in Program.cs](#directly-in-programcs)
21-
- [Feature-style typed Minimal API endpoints with MapGroup](#feature-style-typed-minimal-api-endpoints-with-mapgroup)
20+
- [Minimalistic directly in Program.cs](#minimalistic-directly-in-programcs)
21+
- [Extended in a Feature-style Minimal API with endpoints using MapGroup](#extended-in-a-feature-style-minimal-api-with-endpoints-using-mapgroup)
22+
- [Define record types for Typed Results](#define-record-types-for-typed-results)
2223
- [Extension class anywhere in your project](#extension-class-anywhere-in-your-project)
23-
- [Sample record types](#sample-record-types)
2424
- [Register the endpoints in Program.cs](#register-the-endpoints-in-programcs)
2525

2626
## Quick Links
@@ -210,6 +210,7 @@ Here is a comprehensive `appsettings.json` example covering supported options an
210210
"AuthorizationEndpoint": "https://www.etsy.com/oauth/connect",
211211
"TokenEndpoint": "https://openapi.etsy.com/v3/public/oauth/token",
212212
"UserInformationEndpoint": "https://openapi.etsy.com/v3/application/users/me",
213+
"CallbackPath": "/signin/etsy",
213214
"SaveTokens": true,
214215
"Scopes": [ "shops_r", "email_r" ]
215216
}
@@ -229,54 +230,67 @@ If you bind then from configuration, set the options in code, for example:
229230
builder.Services.AddAuthentication(options =>
230231
{
231232
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
233+
// If you only have Etsy as external provider you can apply it as default challenge scheme
232234
options.DefaultChallengeScheme = EtsyAuthenticationDefaults.AuthenticationScheme;
233235
})
234-
.AddCookie()
236+
.AddCookie(options =>
237+
{
238+
options.LoginPath = "/signin";
239+
options.LogoutPath = "/signout";
240+
})
235241
.AddEtsy(options =>
236242
{
237-
var section = builder.Configuration.GetSection("Etsy");
238-
options.ClientId = section.GetValue<string?>("ClientId")
239-
?? throw new InvalidOperationException("Etsy:ClientId configuration value is missing.");
243+
var section = builder.Configuration.GetSection("Etsy").Get<EtsyAuthenticationOptions>()!;
244+
if (section is not EtsyAuthenticationOptions
245+
// Check if the values from appsettings.json has been properly overridden
246+
|| section.ClientId is "client-id-from-user-secrets")
247+
{
248+
throw new InvalidOperationException("Etsy configuration section is missing or invalid.");
249+
}
240250

241-
// For the Detailed User Info Endpoint and SaveTokens are default values pre-set, but you can override them here
242-
options.IncludeDetailedUserInfo = section.GetValue<bool>("IncludeDetailedUserInfo");
243-
options.SaveTokens = section.GetValue<bool>("SaveTokens");
251+
options.ClientId = section.ClientId;
252+
// Optional: The Etsy App registration provides the `Shared Secret` but it's not documented to be used/required for PKCE flows.
253+
options.ClientSecret = section.ClientSecret;
254+
// Optional: Include detailed user info and auto-mapped claims to get e.g. email, first and last name
255+
options.IncludeDetailedUserInfo = section.IncludeDetailedUserInfo;
244256

245-
// Use the defaults from EtsyAuthenticationDefaults, if you want to repeat them here, but they are set automatically
257+
// Optional: Override the defaults from EtsyAuthenticationDefaults with your own values (not recommended! Will potentially break the handler)
258+
// Here we just re-assign the defaults for demonstration
246259
options.AuthorizationEndpoint = EtsyAuthenticationDefaults.AuthorizationEndpoint;
247260
options.TokenEndpoint = EtsyAuthenticationDefaults.TokenEndpoint;
248261
options.UserInformationEndpoint = EtsyAuthenticationDefaults.UserInformationEndpoint;
249262

250-
// Apply scopes from config if present
251-
var scopes = section.Get<string[]>("Scopes");
263+
// Optional: Override SaveTokens setting from configuration (not recommended to disable! as Etsy API uses refresh tokens)
264+
options.SaveTokens = section.SaveTokens;
252265

253-
if (scopes is not null)
266+
// Optional: Add scopes from configuration
267+
foreach (var scope in section.Scopes)
254268
{
255-
foreach (var scope in scopes)
256-
{
257-
options.Scope.Add(scope);
258-
}
269+
options.Scope.Add(scope);
259270
}
260271

261-
// Optionally Map the image claim
272+
// Optional: Or add scopes manually with provided constants
273+
options.Scope.Add(EtsyAuthenticationConstants.Scopes.TransactionsRead);
274+
275+
// Optional: Map the image claim
262276
options.ClaimActions.MapImageClaim();
263277

264278
// Map other Claims
265-
options.ClaimActions.MapJsonKey("urn:etsy:listingsWrite", "listings_w");
279+
options.ClaimActions.MapJsonKey("urn:etsy:listingsWrite", EtsyAuthenticationConstants.Claims.ListingsWrite);
266280
})
267281
```
268282

269283
## Accessing claims (Minimal API Sample)
270284

271285
If you want to access the claims provided by the Etsy provider, you can set up some Minimal API endpoints like this:
272286

273-
### Directly in Program.cs
287+
### Minimalistic directly in Program.cs
274288

275289
```csharp
276290
using AspNet.Security.OAuth.Etsy;
277291
using System.Security.Claims;
278292

279-
app.MapGet("/profile", (ClaimsPrincipal user) =>
293+
app.MapGet("/etsy/profile", (ClaimsPrincipal user) =>
280294
{
281295
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
282296
var shopId = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ShopId);
@@ -286,10 +300,43 @@ app.MapGet("/profile", (ClaimsPrincipal user) =>
286300
var imageUrl = user.FindFirstValue(EtsyAuthenticationConstants.Claims.ImageUrl);
287301

288302
return Results.Ok(new { userId, shopId, email, firstName, lastName, imageUrl });
289-
}).RequireAuthorization();
303+
})
304+
.RequireAuthorization()
305+
.WithName("EtsyProfile")
306+
.WithSummary("Get authenticated user's Etsy profile information");
307+
```
308+
309+
### Extended in a Feature-style Minimal API with endpoints using MapGroup
310+
311+
This sample assumes you not only have Etsy as external provider and use cookie authentication for session management.
312+
313+
#### Define record types for Typed Results
314+
315+
Before we can start, we need some record types to hold the user profile and token information.
316+
317+
The following ones are created from the json-objects returned by Etsy's API.
318+
319+
```csharp
320+
public sealed record UserInfo
321+
{
322+
public required string UserId { get; init; }
323+
public required string ShopId { get; init; }
324+
public string? Email { get; init; }
325+
public string? FirstName { get; init; }
326+
public string? LastName { get; init; }
327+
public string? ImageUrl { get; init; }
328+
}
329+
330+
public sealed record TokenInfo
331+
{
332+
public string? AccessToken { get; init; }
333+
public string? RefreshToken { get; init; }
334+
public string? ExpiresAt { get; init; }
335+
}
290336
```
291337

292-
### Feature-style typed Minimal API endpoints with MapGroup
338+
> [!NOTE]
339+
> Make sure to add proper JSON serialization attributes if you use System.Text.Json or Newtonsoft.Json to serialize those records to JSON in the HTTP responses.
293340
294341
#### Extension class anywhere in your project
295342

@@ -336,12 +383,11 @@ public static class EtsyAuthEndpoints
336383

337384
private static Results<ChallengeHttpResult, RedirectHttpResult> SignInAsync(string? returnUrl)
338385
=> TypedResults.Challenge(
339-
new AuthenticationProperties { RedirectUri = returnUrl ?? "/" },
340-
new[] { EtsyAuthenticationDefaults.AuthenticationScheme });
386+
new AuthenticationProperties { RedirectUri = returnUrl ?? "/" }, EtsyAuthenticationDefaults.AuthenticationScheme);
341387

342388
private static async Task<RedirectHttpResult> SignOutAsync(HttpContext context)
343389
{
344-
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
390+
await context.SignOutAsync(new AuthenticationProperties { RedirectUri = "/" }, CookieAuthenticationDefaults.AuthenticationScheme);
345391
return TypedResults.Redirect("/");
346392
}
347393

@@ -371,32 +417,13 @@ public static class EtsyAuthEndpoints
371417

372418
return TypedResults.Ok(tokenInfo);
373419
}
374-
```
375-
376-
#### Sample record types
377-
378-
```csharp
379-
public sealed record UserInfo
380-
{
381-
public required string UserId { get; init; }
382-
public required string ShopId { get; init; }
383-
public string? Email { get; init; }
384-
public string? FirstName { get; init; }
385-
public string? LastName { get; init; }
386-
public string? ImageUrl { get; init; }
387-
}
388-
389-
public sealed record TokenInfo
390-
{
391-
public string? AccessToken { get; init; }
392-
public string? RefreshToken { get; init; }
393-
public string? ExpiresAt { get; init; }
394-
}
395420
}
396421
```
397422

398423
#### Register the endpoints in Program.cs
399424

425+
Now that we have defined the extension method to map the Etsy authentication endpoints, we need to register them in our `Program.cs` file.
426+
400427
```csharp
401428
using MyApi.Features.Authorization;
402429
app.MapEtsyAuth();

0 commit comments

Comments
 (0)