You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Identity Server 4与Web API的WPF应用及OidcClient配置咨询

Hey there! Let's walk through your IdentityServer4 + WPF + Web API setup, covering key configuration checks, API call best practices, and common pitfalls to avoid.


OidcClient Configuration Validation & Polish

First, let's validate and refine your existing OidcClient setup to ensure it's robust:

  • Client Registration Match
    Double-check that your IdentityServer4 instance at http://localhost:5000/ has the native.code client registered with these exact settings:

    • Client type set to Native (WPF is a desktop/native app, so this is critical)
    • AllowedGrantTypes includes authorization_code and refresh_token (for token refresh later)
    • AllowedScopes explicitly lists openid profile email fiver_auth_api
    • AllowedRedirectUris precisely matches http://127.0.0.1/sample-wpf-app (avoid mixing localhost and 127.0.0.1—consistency prevents callback failures)
    • AllowOfflineAccess = true (required to get a refresh token for silent token renewal)
  • Response Mode & Flow
    Your choice of FormPost response mode is perfect for embedded WPF browsers—it avoids issues with token data being lost during redirects. The Authorization Code flow is also the secure, recommended choice for native apps (far better than Implicit flow).

  • WpfEmbeddedBrowser Implementation
    Make sure your custom browser class correctly captures the FormPost callback to extract the authorization code and state. Here's a reliable implementation snippet if yours needs tuning:

    public class WpfEmbeddedBrowser : IBrowser
    {
        public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
        {
            var authWindow = new Window
            {
                Width = 600,
                Height = 800,
                WindowStartupLocation = WindowStartupLocation.CenterOwner,
                Title = "Login to IdentityServer"
            };
            var webView = new WebBrowser();
            authWindow.Content = webView;
    
            var tcs = new TaskCompletionSource<BrowserResult>();
    
            webView.DocumentCompleted += (s, e) =>
            {
                // Capture FormPost data when redirecting to our callback URI
                if (webView.Url.AbsoluteUri.StartsWith(options.RedirectUri))
                {
                    var form = webView.Document.Forms[0];
                    var code = form.All["code"]?.GetAttribute("value");
                    var state = form.All["state"]?.GetAttribute("value");
    
                    tcs.SetResult(new BrowserResult
                    {
                        Response = $"{options.RedirectUri}?code={code}&state={state}",
                        ResultType = BrowserResultType.Success
                    });
                    authWindow.Close();
                }
            };
    
            webView.Navigate(options.StartUrl);
            authWindow.ShowDialog();
    
            return await tcs.Task;
        }
    }
    

Complete Web API Call Implementation

Your CallButtonAsync method needs to handle token retrieval, authorization headers, and token refresh for a smooth user experience. Here's the full implementation:

Step 1: Complete the Authentication Flow

First, finish the login process and store tokens securely:

// Initialize OidcClient (store this as a class-level variable for reuse)
private OidcClient _oidcClient;
private string _accessToken;
private string _refreshToken;

private async void LoginButton_Click(object sender, RoutedEventArgs e)
{
    var options = new OidcClientOptions
    {
        Authority = "http://localhost:5000/",
        ClientId = "native.code",
        Scope = "openid profile email fiver_auth_api",
        RedirectUri = "http://127.0.0.1/sample-wpf-app",
        ResponseMode = OidcClientOptions.AuthorizeResponseMode.FormPost,
        Flow = OidcClientOptions.AuthenticationFlow.AuthorizationCode,
        Browser = new WpfEmbeddedBrowser()
    };
    _oidcClient = new OidcClient(options);

    var loginResult = await _oidcClient.LoginAsync(new LoginRequest());
    if (loginResult.IsError)
    {
        MessageBox.Show($"Login failed: {loginResult.Error}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        return;
    }

    // Store tokens securely (use Windows Credential Manager for production!)
    _accessToken = loginResult.AccessToken;
    _refreshToken = loginResult.RefreshToken;
}

Step 2: Refine the API Call Method

Now, update your API call to handle authorization, errors, and token refresh:

private async void CallApiButton_Click(object sender, RoutedEventArgs e)
{
    await CallButtonAsync();
}

private async Task CallButtonAsync()
{
    if (string.IsNullOrEmpty(_accessToken))
    {
        MessageBox.Show("Please log in first!", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
        return;
    }

    using var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken);

    try
    {
        var response = await httpClient.GetAsync("http://your-api-url/api/protected-endpoint");
        
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            MessageBox.Show($"API Response:\n{content}", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
        }
        else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            // Attempt to refresh the access token
            var refreshResult = await _oidcClient.RefreshTokenAsync(_refreshToken);
            if (!refreshResult.IsError)
            {
                _accessToken = refreshResult.AccessToken;
                _refreshToken = refreshResult.RefreshToken;
                
                // Retry the API call with the new token
                httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken);
                var retryResponse = await httpClient.GetAsync("http://your-api-url/api/protected-endpoint");
                
                if (retryResponse.IsSuccessStatusCode)
                {
                    var retryContent = await retryResponse.Content.ReadAsStringAsync();
                    MessageBox.Show($"API Response (after token refresh):\n{retryContent}", "Success", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else
                {
                    MessageBox.Show("Token refresh succeeded but API call still failed.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            else
            {
                MessageBox.Show("Session expired. Please log in again.", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
                // Trigger login flow here
            }
        }
        else
        {
            MessageBox.Show($"API call failed: {response.StatusCode}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"Error calling API: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

Critical Additional Best Practices
  • Secure Token Storage
    Never store tokens in plaintext files or unprotected memory. For WPF apps, use Windows.Security.Credentials.PasswordVault to store tokens securely in the Windows Credential Manager.

  • Web API Token Validation
    Ensure your Web API is configured to validate IdentityServer4 tokens. Add this to your API's Startup.cs:

    services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = "http://localhost:5000/";
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false // Set to true if your API has a specific audience
            };
        });
    
    // Enable authorization middleware
    app.UseAuthentication();
    app.UseAuthorization();
    
  • Scope-Based Authorization
    Restrict API access to only clients with the fiver_auth_api scope. Add a policy in your API's Startup.cs:

    services.AddAuthorization(options =>
    {
        options.AddPolicy("RequireFiverAuthApiScope", policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.RequireClaim("scope", "fiver_auth_api");
        });
    });
    

    Then apply it to your API endpoints:

    [Authorize(Policy = "RequireFiverAuthApiScope")]
    [ApiController]
    [Route("api/[controller]")]
    public class ProtectedController : ControllerBase
    {
        // Your endpoint logic here
    }
    

内容的提问来源于stack exchange,提问作者Alexandra Damaschin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:28:28