基于Winforms+C#的Upwork API桌面应用授权登录方式咨询
Hey there! I’ve tackled this exact scenario before—Upwork’s docs do a poor job of calling out desktop-specific OAuth flows, so let me break down how to get this working for your WinForms app.
Upwork uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) for desktop/mobile apps (since you can’t securely store a client secret in a desktop binary). Here’s the step-by-step implementation:
1. First, Configure Your Upwork Developer App
- Head to your Upwork Developer Dashboard and create/edit your app.
- Set the app type to Desktop/Mobile (this enables PKCE support).
- Add a custom redirect URI (e.g.,
myupworkapp://authcallback)—you’ll need this to capture the authorization code after the user logs in.
2. Implement the PKCE Flow in C#
Step 1: Generate PKCE Code Verifier & Challenge
You’ll need these to secure the authorization flow (no client secret required):
using System.Security.Cryptography; using System.Text; using System.Web; // Generate a random code verifier (32-128 chars, Base64URL encoded) private string GenerateCodeVerifier() { var randomBytes = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(randomBytes); return Convert.ToBase64String(randomBytes) .Replace('+', '-') .Replace('/', '_') .TrimEnd('='); } // Generate the code challenge from the verifier (SHA256 + Base64URL) private string GenerateCodeChallenge(string codeVerifier) { using var sha256 = SHA256.Create(); var verifierBytes = Encoding.UTF8.GetBytes(codeVerifier); var hashBytes = sha256.ComputeHash(verifierBytes); return Convert.ToBase64String(hashBytes) .Replace('+', '-') .Replace('/', '_') .TrimEnd('='); }
Step 2: Launch the Authorization URL
Use the user’s default browser (or a WinForms WebBrowser control) to open the Upwork authorization page:
private void StartAuthorization() { var clientId = "YOUR_UPWORK_CLIENT_ID"; var redirectUri = "myupworkapp://authcallback"; var codeVerifier = GenerateCodeVerifier(); var codeChallenge = GenerateCodeChallenge(codeVerifier); var scope = "api:read"; // Add scopes you need, e.g., "api:read api:write" // Store the code verifier temporarily (e.g., in a static variable or app settings) _tempCodeVerifier = codeVerifier; var authUrl = $"https://www.upwork.com/api/auth/v1/authorize?" + $"client_id={HttpUtility.UrlEncode(clientId)}" + $"&redirect_uri={HttpUtility.UrlEncode(redirectUri)}" + $"&response_type=code" + $"&code_challenge={HttpUtility.UrlEncode(codeChallenge)}" + $"&code_challenge_method=S256" + $"&scope={HttpUtility.UrlEncode(scope)}"; // Open the URL in default browser System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(authUrl) { UseShellExecute = true }); }
Step 3: Capture the Authorization Code via Custom URI Scheme
Windows needs to know that your app handles the myupworkapp:// scheme. You can register this temporarily on app startup (or via installer):
// Add this to your app's startup logic (run as admin if needed) private void RegisterCustomUriScheme() { var appPath = System.Reflection.Assembly.GetExecutingAssembly().Location; using var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\Classes\myupworkapp"); key.SetValue("", "URL:My Upwork App"); key.SetValue("URL Protocol", ""); using var shellKey = key.CreateSubKey(@"shell\open\command"); shellKey.SetValue("", $"\"{appPath}\" \"%1\""); }
Then, in your Program.cs, handle the command-line argument that contains the callback URL:
static class Program { [STAThread] static void Main(string[] args) { Application.SetHighDpiMode(HighDpiMode.SystemAware); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var mainForm = new MainForm(); // Check if we're being launched via the OAuth callback if (args.Length > 0 && args[0].StartsWith("myupworkapp://authcallback")) { // Parse the authorization code from the callback URL var callbackUrl = new Uri(args[0]); var code = HttpUtility.ParseQueryString(callbackUrl.Query).Get("code"); mainForm.HandleAuthorizationCode(code); } Application.Run(mainForm); } }
Step 4: Exchange Code for Access Token
Once you have the authorization code, exchange it (along with the saved code verifier) for an access token:
using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; private async Task<string> ExchangeCodeForToken(string authorizationCode) { var clientId = "YOUR_UPWORK_CLIENT_ID"; var redirectUri = "myupworkapp://authcallback"; using var client = new HttpClient(); var formData = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("grant_type", "authorization_code"), new KeyValuePair<string, string>("client_id", clientId), new KeyValuePair<string, string>("code", authorizationCode), new KeyValuePair<string, string>("redirect_uri", redirectUri), new KeyValuePair<string, string>("code_verifier", _tempCodeVerifier) }); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = await client.PostAsync("https://www.upwork.com/api/auth/v1/token", formData); response.EnsureSuccessStatusCode(); var tokenJson = await response.Content.ReadAsStringAsync(); // Parse tokenJson to get access_token, refresh_token, expires_in, etc. // You can use Newtonsoft.Json or System.Text.Json for parsing return tokenJson; } // Call this from your form when you receive the code public void HandleAuthorizationCode(string code) { _ = ExchangeCodeForToken(code).ContinueWith(task => { if (task.IsCompletedSuccessfully) { var tokenData = task.Result; // Save the access/refresh tokens securely (e.g., using Windows Credential Manager) // Now you can use the access token to call Upwork API endpoints } }, TaskScheduler.FromCurrentSynchronizationContext()); }
3. Key Notes
- Scopes: Make sure you request only the scopes your app actually needs—Upwork may restrict access to certain scopes until your app is approved.
- Token Storage: Never store access/refresh tokens in plain text. Use Windows Credential Manager or encrypted app settings to keep them secure.
- Refresh Tokens: When your access token expires, use the refresh token to get a new one without prompting the user again (follow similar steps but use
grant_type=refresh_token).
内容的提问来源于stack exchange,提问作者Valery Yegorov

