An ASP.NET Core (Razor Pages + minimal APIs) port of the Node.js sample, demonstrating the OAuth 2.0 authorization code flow used by the Recruit Wizard API.
This is one of several language samples in this repository. See the top-level README for the full list.
- Redirect the user to
GET {Host}/api/connect/authorize(opened in a new tab) - Recruit Wizard redirects back to the registered callback URL, where the
sample displays the authorization
codeon a copy-friendly page - Paste the code into the input on the main tab and exchange it for tokens
via
POST {Host}/api/connect/token(grant_type=authorization_code) - Refresh access tokens with
grant_type=refresh_token - Call protected endpoints (
/api/ServerStatus,/api/Users/{id}) using the access token in anAuthorization: Bearer …header
- .NET 9 SDK (or newer)
- A Recruit Wizard API client (
ClientId+ClientSecret) with a redirect URI that Recruit Wizard support has white-listed on your client. The sample defaults to ahttps://webhook.site/<uuid>placeholder so you don't need to expose localhost via ngrok or a similar tunnel — see "Testing with webhook.site" below.
Heads-up: redirect URIs are white-listed per client by Recruit Wizard. If the URL you want to use isn't already registered, send it to Recruit Wizard support and ask them to add it to your client — otherwise the authorize step will fail with a
redirect_urimismatch.
From this folder (dotnet/):
dotnet restore
# Configure your client (pick ONE of the options below — see "Configuration")
dotnet runThen open http://localhost:3000.
The default
applicationUrlishttp://localhost:3000(the local server the browser opens to use the sample UI — not the OAuth redirect URI). If port 3000 is already in use, pass--urls http://localhost:5050or similar; the redirect URI is independent.
Recruit Wizard typically white-lists HTTPS redirect URIs only, so a plain
http://localhost:…/auth/callback won't work without exposing your machine
through ngrok or a similar tunnel. The simplest workaround — and the default
this sample ships with — is to use webhook.site as a
disposable callback target.
-
Open https://webhook.site and copy the unique URL it gives you, e.g.
https://webhook.site/e9afcb42-4a99-45f3-a92d-dec5d1dd19c5. -
Send that URL to Recruit Wizard support and ask them to add it as an allowed redirect URI on your client. Recruit Wizard validates the
redirect_uriyou send in the authorize request against this white-list, so the authorize step will fail until support has registered it. -
Point the sample at the same URL so the authorize link it builds matches what you registered. For example, with user secrets:
dotnet user-secrets set "RecruitWizard:RedirectUri" "https://webhook.site/e9afcb42-4a99-45f3-a92d-dec5d1dd19c5"
Or in
appsettings.json:"RecruitWizard": { "RedirectUri": "https://webhook.site/e9afcb42-4a99-45f3-a92d-dec5d1dd19c5" }
-
Once support has confirmed the URL is white-listed,
dotnet run, open http://localhost:3000, click Connect to Recruit Wizard, and sign in. Recruit Wizard will redirect to webhook.site instead of back to the sample app. -
In the webhook.site tab, look at the latest request's query string — you'll see
?code=…&state=…. Copy thecodevalue. -
Switch back to the sample app's main tab, paste the code into the Exchange code for token input, and submit.
The local
/auth/callbackpage is skipped in this mode (the redirect lands on webhook.site, not on your machine), so the built-instatecheck is bypassed too. That's fine for ad-hoc testing — just don't ship a flow like this to production.
The sample reads its OAuth client config from the standard ASP.NET Core configuration pipeline. Use whichever option you prefer:
Edit the RecruitWizard section:
{
"RecruitWizard": {
"Host": "https://sandbox.recruitwizard.site",
"ClientId": "your-client-id-here",
"ClientSecret": "your-client-secret-here",
"RedirectUri": "https://webhook.site/your-unique-id-here"
}
}Keeps your client secret out of source control:
dotnet user-secrets init
dotnet user-secrets set "RecruitWizard:ClientId" "your-client-id-here"
dotnet user-secrets set "RecruitWizard:ClientSecret" "your-client-secret-here"
dotnet user-secrets set "RecruitWizard:Host" "https://sandbox.recruitwizard.site"
dotnet user-secrets set "RecruitWizard:RedirectUri" "https://webhook.site/your-unique-id-here"Names use the standard : → __ mapping:
$env:RecruitWizard__Host = "https://sandbox.recruitwizard.site"
$env:RecruitWizard__ClientId = "your-client-id-here"
$env:RecruitWizard__ClientSecret = "your-client-secret-here"
$env:RecruitWizard__RedirectUri = "https://webhook.site/your-unique-id-here"
dotnet runSee .env.example for the full list.
| Setting | Description |
|---|---|
RecruitWizard:Host |
Base URL of the Recruit Wizard API (e.g. https://sandbox.recruitwizard.site) |
RecruitWizard:ClientId |
OAuth client ID issued by Recruit Wizard |
RecruitWizard:ClientSecret |
OAuth client secret issued by Recruit Wizard |
RecruitWizard:RedirectUri |
Callback URL — must exactly match one of the URLs registered with your client |
ASPNETCORE_URLS (optional) |
Override the listening URL/port (default http://localhost:3000) |
| Path | Method | What it does |
|---|---|---|
/ |
GET | Home page — current token state, paste-code input, action buttons |
/auth/login |
GET | Generates a state, redirects to the authorize endpoint |
/auth/callback |
GET | Lands here from Recruit Wizard; shows the code with a Copy button |
/auth/exchange |
POST | Exchanges the pasted code for an access + refresh token |
/auth/refresh |
POST | Uses the stored refresh token to mint a new access token |
/auth/logout |
POST | Clears the in-memory tokens |
/api/server-status |
POST | Calls GET /api/ServerStatus using the access token |
/api/me |
POST | Decodes the JWT, then calls GET /api/Users/{UserID} |
/assets/* |
GET | Static files served from wwwroot/assets/ (logo, etc.) |
dotnet/
├── RecruitWizard.AuthSample.csproj
├── Program.cs # Routes, DI wiring, startup logging
├── appsettings.json # Configuration (RecruitWizard section)
├── appsettings.Development.json
├── .env.example
├── Properties/
│ └── launchSettings.json
├── Services/
│ ├── AuthService.cs # Authorize URL, token + refresh calls, JWT decode
│ ├── RecruitWizardOptions.cs # Strongly typed config (validated on startup)
│ ├── StateStore.cs # CSRF state generation/validation
│ └── TokenStore.cs # In-memory token + last-call storage
├── Pages/
│ ├── _ViewImports.cshtml
│ ├── _ViewStart.cshtml
│ ├── Index.cshtml # Home page (paste-code UI)
│ ├── Index.cshtml.cs
│ ├── Callback.cshtml # Page shown after Recruit Wizard redirects back
│ └── Callback.cshtml.cs
└── wwwroot/
└── assets/
└── full-logo-dark.png
- Tokens are kept in memory only. Restart the process and you start fresh. In a real application, persist them per-user in a database (encrypted at rest).
- The
stateparameter is generated per login attempt and validated on the callback to mitigate CSRF. - The JWT is decoded with a small base64url helper for display purposes only —
the signature is not verified. For real authentication, use
Microsoft.AspNetCore.Authentication.JwtBeareragainst Recruit Wizard's JWKS. - The code is intentionally framework-light so it's easy to lift into your own app.