如何通过Angular UI与.NET Core后端采用OAuth流访问VSTS API?
Absolutely, this setup is completely achievable—even if you can’t find a single end-to-end example, all the building blocks exist and play nicely together. Let’s break down how to implement OAuth for Azure DevOps (formerly VSTS) with an Angular frontend and .NET Core backend:
The key here is to keep sensitive credentials (like your app’s client secret) out of the frontend. The flow will be:
- Frontend redirects users to Azure DevOps’ OAuth authorization page
- User grants access, and Azure DevOps sends an authorization code to your backend’s callback endpoint
- Backend exchanges this code for an access token (and refresh token)
- Backend either uses the token to call Azure DevOps APIs directly (and returns data to the frontend) or securely passes a limited token to the frontend for direct calls (though proxying via backend is safer)
1. Register Your Azure DevOps App
First, create an app registration in Azure DevOps:
- Go to your Azure DevOps organization settings → OAuth Configuration → New Application
- Fill in the name, set the callback URL to your backend’s endpoint (e.g.,
https://your-backend/api/auth/azure-devops-callback) - Note down the Client ID and Client Secret (keep this secret safe—never expose it to the frontend)
- Select the required scopes (e.g.,
vso.project_readfor reading projects,vso.work_writefor modifying work items)
2. Configure OAuth in .NET Core
You can use the built-in authentication middleware or handle the code flow manually. Here’s a simplified example using manual handling (more flexible for custom logic):
Add Required Packages
Install these NuGet packages:
Install-Package Microsoft.AspNetCore.Http.Extensions Install-Package System.Net.Http.Json
Create a Callback Controller
[ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly IConfiguration _config; private readonly HttpClient _httpClient; public AuthController(IConfiguration config, HttpClient httpClient) { _config = config; _httpClient = httpClient; } // Endpoint to receive the authorization code from Azure DevOps [HttpGet("azure-devops-callback")] public async Task<IActionResult> AzureDevOpsCallback([FromQuery] string code) { // Exchange code for access token var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string> { ["client_id"] = _config["AzureDevOps:ClientId"], ["client_secret"] = _config["AzureDevOps:ClientSecret"], ["code"] = code, ["grant_type"] = "authorization_code", ["redirect_uri"] = _config["AzureDevOps:CallbackUrl"] }); var tokenResponse = await _httpClient.PostAsync("https://app.vssps.visualstudio.com/oauth2/token", tokenRequest); var tokenData = await tokenResponse.Content.ReadFromJsonAsync<AzureDevOpsToken>(); // Store the access token and refresh token (e.g., in a database linked to the user) // ... your storage logic here ... // Redirect back to frontend with a success flag return Redirect($"{_config["FrontendUrl"]}/auth-success"); } // Example proxy endpoint to call Azure DevOps API [HttpGet("azure-devops/projects")] public async Task<IActionResult> GetProjects(string userId) { // Retrieve the stored access token for the user var accessToken = await GetStoredToken(userId); var apiResponse = await _httpClient.GetAsync("https://dev.azure.com/your-org/_apis/projects?api-version=7.1-preview.4", new HttpRequestMessageOptions { Headers = { { "Authorization", $"Bearer {accessToken}" } } }); var projects = await apiResponse.Content.ReadFromJsonAsync<AzureDevOpsProjects>(); return Ok(projects); } // Helper method to fetch stored token private async Task<string> GetStoredToken(string userId) { // Implement your token retrieval logic (database/cache) return "your-stored-access-token"; } } // Helper classes for deserialization public class AzureDevOpsToken { public string access_token { get; set; } public string refresh_token { get; set; } public int expires_in { get; set; } } public class AzureDevOpsProjects { public List<Project> value { get; set; } } public class Project { public string id { get; set; } public string name { get; set; } }
Add Configuration to appsettings.json
{ "AzureDevOps": { "ClientId": "your-client-id", "ClientSecret": "your-client-secret", "CallbackUrl": "https://your-backend/api/auth/azure-devops-callback", "OrgUrl": "https://dev.azure.com/your-org" }, "FrontendUrl": "https://your-angular-app" }
1. Create an Authorization Redirect
Add a button or link in your Angular component to send users to Azure DevOps’ authorization page:
Component Template
<button (click)="connectToAzureDevOps()">Connect to Azure DevOps</button>
Component Logic
import { Component } from '@angular/core'; import { environment } from '../environments/environment'; @Component({ selector: 'app-auth', templateUrl: './auth.component.html' }) export class AuthComponent { connectToAzureDevOps(): void { const authUrl = `https://app.vssps.visualstudio.com/oauth2/authorize?client_id=${environment.azureDevOpsClientId}&response_type=code&redirect_uri=${environment.azureDevOpsCallbackUrl}&scope=vso.project_read`; window.location.href = authUrl; } }
Add Environment Variables
In src/environments/environment.ts:
export const environment = { production: false, azureDevOpsClientId: 'your-client-id', azureDevOpsCallbackUrl: 'https://your-backend/api/auth/azure-devops-callback' };
2. Handle Post-Authorization Flow
After the user grants access, they’ll be redirected back to your frontend (via the backend’s callback redirect). You can then call your backend’s proxy endpoints to fetch Azure DevOps data:
// Example service to call backend proxy import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class AzureDevOpsService { constructor(private http: HttpClient) {} getProjects(userId: string) { return this.http.get(`https://your-backend/api/auth/azure-devops/projects?userId=${userId}`); } }
- Token Refresh: Azure DevOps access tokens expire quickly (usually 1 hour). Implement logic in your backend to use the refresh token to get a new access token automatically when needed.
- Security: Never expose the client secret to the frontend. All token exchanges must happen server-side.
- Scopes: Only request the scopes your app actually needs—this reduces security risk and makes users more likely to grant access.
- Azure DevOps Naming: Remember that VSTS was rebranded to Azure DevOps, so all modern documentation uses this name, and API endpoints now use
dev.azure.cominstead ofvisualstudio.com.
内容的提问来源于stack exchange,提问作者Nikheel

