OAuth Callbacks
The ingress package gives you the callback endpoint for Discord's authorization-code flow.
Use the root namespace to register the route and the DisCatSharp.Hosting.AspNetCore.Ingress.OAuth namespace when you need the callback contracts and result types.
The package owns the callback handling step. Your application still owns the part that starts the OAuth flow.
Flow summary
- generate a secure
state - create an authorize URL with DiscordOAuth2Client
- store the pending request in IDiscordIngressPendingStateStore
- redirect the user to Discord
- let the ingress callback handler validate the query and exchange the code
Register the services
using DisCatSharp;
using DisCatSharp.Hosting.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new DiscordOAuth2Client(
123456789012345678,
"YOUR_CLIENT_SECRET",
"https://bot.example.com/discord/oauth/callback"));
builder.Services.AddDisCatSharpAspNetCore(
configureOAuth: options =>
{
options.ClientId = 123456789012345678;
options.ClientSecret = "YOUR_CLIENT_SECRET";
options.RedirectUri = "https://bot.example.com/discord/oauth/callback";
});
var app = builder.Build();
Start the flow
Your app is responsible for issuing the redirect and storing the pending state:
using DisCatSharp;
using DisCatSharp.Hosting.AspNetCore.Ingress;
using DisCatSharp.Hosting.AspNetCore.Ingress.OAuth;
app.MapGet(
"/login/discord",
async (DiscordOAuth2Client oauthClient, IDiscordIngressPendingStateStore pendingStates, TimeProvider timeProvider) =>
{
var state = oauthClient.GenerateState();
var authorizeUri = oauthClient.GenerateOAuth2Url("identify guilds", state);
var now = timeProvider.GetUtcNow();
await pendingStates.StoreAsync(new DiscordIngressPendingState
{
Key = state,
Flow = DiscordOAuthIngressOptions.DefaultPendingStateFlow,
RequestUri = authorizeUri,
CreatedAt = now,
ExpiresAt = now.AddMinutes(15)
});
return Results.Redirect(authorizeUri.AbsoluteUri);
});
Persist the original RequestUri. The callback flow uses it to preserve the originally requested scope, the integration_type, and redirect URI consistency checks.
Map the callback endpoint
app.MapDisCatSharpIngress();
Or, if you only want the OAuth surface:
app.MapDiscordOAuthIngress();
The callback endpoint then:
- validates the callback query
- consumes the pending state
- verifies the configured redirect URI
- exchanges the authorization code with Discord
- returns a structured ingress response
Built-in callback response
The default callback endpoint emits JSON.
Successful payloads include fields such as:
statusrequested_scopegranted_scopeintegration_typeincoming_webhook_available- callback and redirect URI metadata
Failures keep the same structured shape and include the failure classification and detail.
If you need the full in-memory model instead of the HTTP payload, use DiscordOAuthCallbackResult.
Custom callback behavior
If you need a custom success page, redirect, session sign-in, or persistence flow, choose one of these extension points:
- replace IDiscordOAuthCallbackResponseFactory to keep the built-in route but change the emitted HTTP response
- call IDiscordOAuthCallbackHandler yourself from a custom route and use the returned DiscordOAuthCallbackResult
That keeps the validation and token-exchange logic reusable while letting your app own the final user experience.
Existing app vs self-host
The callback logic is shared across both hosting modes.
- existing ASP.NET Core app: register the services, then map
MapDisCatSharpIngress()orMapDiscordOAuthIngress() - self-host mode: call
AddDisCatSharpAspNetCoreSelfHost(...), then configure DiscordOAuthIngressOptions through DI before the host starts
The self-host wrapper only provides the internal ASP.NET Core app. The OAuth contracts, pending-state store, and callback handler are the same services either way.
For the full package map, see Web Ingress Overview.