VSTS Web Api gives 403 unauthoratative response - azure-devops

I have created a native app in Azure using App Registration and added required permissions for VSTS Agent
Now, I am able to login using oauth authentication with "ADAL" and able to get the token but when trying to access the web api request it gives Http 403 Response
Here is the code example below
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(vstsCollectionUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.Authorization = authHeader;
HttpResponseMessage res = client.GetAsync(webapiURL).Result;
}
Please let me know what i am missing
thanks in advance

Seems it's an authentication issue, Just check the value of authHeader.
You can reference below samples for the authentication with header:
Sample 1- auth-samples here ;
Sample 2 - REST API
Post the auth sample as reference here:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
namespace DeviceProfileSample
{
public class Program
{
//============= Config [Edit these with your settings] =====================
internal const string vstsCollectionUrl = "https://myaccount.visualstudio.com"; //change to the URL of your VSTS account; NOTE: This must use HTTPS
internal const string clientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1"; //update this with your Application ID from step 2.6 (do not change this if you have an MSA backed account)
//==========================================================================
internal const string VSTSResourceId = "499b84ac-1321-427f-aa17-267ca6975798"; //Static value to target VSTS. Do not change
public static void Main(string[] args)
{
AuthenticationContext ctx = GetAuthenticationContext(null);
AuthenticationResult result = null;
try
{
DeviceCodeResult codeResult = ctx.AcquireDeviceCodeAsync(VSTSResourceId, clientId).Result;
Console.WriteLine("You need to sign in.");
Console.WriteLine("Message: " + codeResult.Message + "\n");
result = ctx.AcquireTokenByDeviceCodeAsync(codeResult).Result;
var bearerAuthHeader = new AuthenticationHeaderValue("Bearer", result.AccessToken);
ListProjects(bearerAuthHeader);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Something went wrong.");
Console.WriteLine("Message: " + ex.Message + "\n");
}
}
private static AuthenticationContext GetAuthenticationContext(string tenant)
{
AuthenticationContext ctx = null;
if (tenant != null)
ctx = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
else
{
ctx = new AuthenticationContext("https://login.windows.net/common");
if (ctx.TokenCache.Count > 0)
{
string homeTenant = ctx.TokenCache.ReadItems().First().TenantId;
ctx = new AuthenticationContext("https://login.microsoftonline.com/" + homeTenant);
}
}
return ctx;
}
private static void ListProjects(AuthenticationHeaderValue authHeader)
{
// use the httpclient
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(vstsCollectionUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("User-Agent", "VstsRestApiSamples");
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.Authorization = authHeader;
// connect to the REST endpoint
HttpResponseMessage response = client.GetAsync("_apis/projects?stateFilter=All&api-version=2.2").Result;
// check to see if we have a succesfull respond
if (response.IsSuccessStatusCode)
{
Console.WriteLine("\tSuccesful REST call");
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
throw new UnauthorizedAccessException();
}
else
{
Console.WriteLine("{0}:{1}", response.StatusCode, response.ReasonPhrase);
}
}
}
}
}

Aren't you missing what authentication,
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", token)));
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(vstsCollectionUrl);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials)
HttpResponseMessage response = client.GetAsync(uri).Result;
response.EnsureSuccessStatusCode();
var responseStream = await response.Content.ReadAsStreamAsync();
}
I hope this helps.

Related

How do I get the JWT in a Blazor Server App with Microsoft Identity Platform (AAD) to make external API-Management call and authorize with the jwt

The situation I have:
Blazor Server App , .Net6.0.9 with Microsoft Identity Platform.
Blazor Server App is registered in the App Registration on Tenant-1
Client-API-1 is also resigered in the App Registration on Tenant-1
Login actions are done against/with the ClientId of the Client-API-1 registration and work fine.
In the API-Management I've added on the Inbound processing Polecies Validate-jwt like this:
(source of Microsoft)
<policies>
<inbound>
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid!!!">
<openid-config url="https://login.microsoftonline.com/11a14169-89cc-44e8-95d7-xxxxxxxxxxxx/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud">
<value>{client-id-of-Client-API-1-on-App-Registration}</value>
</claim>
</required-claims>
</validate-jwt>
In Service looks like this:
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Identity.Web;
using System.Net.Http.Headers;
namespace BlazorSAAppJwt.Data
{
public class ApimService : IApimService
{
private AuthenticationStateProvider _authenticationStateProvider { get; set; }
private readonly ITokenAcquisition _tokenAcquisition;
public ApimService(AuthenticationStateProvider AuthenticationStateProvider, ITokenAcquisition tokenAcquisition)
{
_authenticationStateProvider = AuthenticationStateProvider;
_tokenAcquisition = tokenAcquisition;
}
//public async Task<string?> GetResponseAsync(string path, CancellationToken cancellationToken)
public async Task<string?> GetResponseAsync(string path)
{
try
{
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
if (authState?.User?.Identity?.IsAuthenticated ?? false)
{
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://apimanagement.azure-api.net/");
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpClient.DefaultRequestHeaders.Add("email", authState.User.Identity.Name);
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "{My APIM suvbscriptionkey}"); // APIM
httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Trace", "true");
// This gets the UserToken to get data from Microsoft Graph for the scopes: User.Read & Mail.Read
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "Mail.Read" });
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
var dataRequest = await httpClient.GetAsync("https://graph.microsoft.com/beta/me");
string? userDisplayName = "";
if (dataRequest.IsSuccessStatusCode)
{
var userData = System.Text.Json.JsonDocument.Parse(await dataRequest.Content.ReadAsStreamAsync());
userDisplayName = userData.RootElement.GetProperty("displayName").GetString();
}
//Remove the previous Authorization-header for the Microsoft Graph call
httpClient.DefaultRequestHeaders.Remove("Authorization");
//Add the Application token to the Authorization for APIM
//NOTE!!! Here is where the JWT token should be used!!!!
string jwt = "How do I get the jwt here to add and send to the APIM";
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}");
//HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}", cancellationToken);
HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}");
if (response.IsSuccessStatusCode)
{
string clientApiResult = await response.Content.ReadAsStringAsync();
return clientApiResult;
}
else
{
throw new UnauthorizedAccessException($"(Graph) User Display Name: {userDisplayName}" +
$"{Environment.NewLine}Response from APIM call: {response}");
}
}
else
{
// "The user is NOT authenticated.";
throw new UnauthorizedAccessException();
}
return default;
}
catch (Exception ex)
{
var iets = ex.Message;
throw;
}
}
}
}
I receive the UserDisplayName from the Graph API-call.
My program.cs
using BlazorSAAppJwt.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
var initialScopes = builder.Configuration["DownstreamApi:Scopes"]?.Split(' ') ?? builder.Configuration["MicrosoftGraph:Scopes"]?.Split(' ');
var azureSection = builder.Configuration.GetSection("AzureAd");
var microsoftGraphSection = builder.Configuration.GetSection("MicrosoftGraph");
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
//.AddMicrosoftGraph(microsoftGraphSection) // Nuget Microsoft.Identity.Web.MicrosoftGraph
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddTokenAcquisition();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<ApimService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
What do I miss, and how do I setup my Blasor Server App to use the JWT token?
EDIT:
The API-calls on the APIM is not going to change and will call the Client-Api that is not exposed to the internet.
Who knows that the call:
var token = await _tokenAcquisition.GetAccessTokenForUserAsync(new string[] { "User.Read", "Mail.Read" });
Retruns the JWT... It does and what it is you need to use in the request header
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(token);
And make the call
HttpResponseMessage response = await httpClient.GetAsync($"{path.ToLower()}", cancellationToken);

trust failure when trying to access my Kestrel-based REST server

I'm experimenting with a Xamarin app, which should access a .NET Core REST server.
I ran into this issue when switching to https; I can access the api from Chrome no problem, but if I try so from within my app, I get a System.Net.WebException saying
'Error: TrustFailure (A call to SSPI failed, see inner exception.)'.
I setup my server like this:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var host = WebHost.CreateDefaultBuilder(args)
.UseUrls("https://*:5000")
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>();
return host as IWebHostBuilder;
}
and in my app, I do something like this:
public bool addUser(User user)
{
var request = WebRequest.Create("https://192.168.1.79:5000/api/users");
request.ContentType = "application/json";
request.Method = "POST";
try
{
var json = JsonConvert.SerializeObject(user);
var data = Encoding.UTF8.GetBytes(json);
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
return response.StatusCode == HttpStatusCode.Created;
}
}
catch(Exception ecx)
{
var what = ecx.Message;
return false;
}
}
Thanks so much for any help!

How to Access using Xamarin.Forms local Web API with Emulator for Visual Studio?

I've created .NET Framework API which contains authentication, I launch the Web API using Jetbrains Rider and I run my Xamarin.Forms application using Visual Studio and I can't access any data from my Web API nor post any.
The Webservice class:
private readonly HttpClient _client;
public AccountService()
{
_client = new HttpClient
{
MaxResponseContentBufferSize = 256000
};
}
public async Task RegisterAsync(string email, string password, string confirmPassword)
{
var url = "http://169.254.80.80:61348/api/Account/Register";
var model = new RegisterBindingModel()
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _client.PostAsync(url, content);
}
The initiation of the registration
private async void Register()
{
try
{
using (UserDialogs.Instance.Loading())
{
await _accountServices.RegisterAsync
(Email, Password, ConfirmPassword);
}
UserDialogs.Instance.Alert("Register Successful");
await _navigation.PushAsync(new LoginPage());
}
catch
{
UserDialogs.Instance.Alert("Something wrong happened, Try again");
}
}
I've tried to access the localhost through Emulator using these IPs:
10.0.3.2
10.0.2.2
169.254.80.80
And I've tried my default gateways and my local IP address with and without ports, in regardless using postman i can work with my api flawlessly.
I don't get errors but the connection status code is not successful and I don't get any data and the newly registered account won't be posted to the api.
EDIT:
As for the answers I've changed my method to this:
public async Task<string> RegisterAsync(string email, string password, string confirmPassword)
{
var client = new HttpClient()
{
BaseAddress = new Uri("http://169.254.80.80:61348/")
};
var postData = new List<KeyValuePair<string, string>>();
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("email", email));
nvc.Add(new KeyValuePair<string, string>("password", password));
nvc.Add(new KeyValuePair<string, string>("confirmPassword", confirmPassword));
var req = new HttpRequestMessage(HttpMethod.Post, "api/Account/Register") { Content = new FormUrlEncodedContent(nvc) };
var res = await client.SendAsync(req);
if (res.IsSuccessStatusCode)
{
string result = await res.Content.ReadAsStringAsync();
string test = JsonConvert.DeserializeObject<string>(result);
return test;
}
return null;
}
and i call the web api using postman like this:
http://localhost:61348/api/Account/Register
I always prefer Newtonsoft Json.NET to carry out web request here is the code I have implemented in my case and it works great for me.
public static async Task<string> ResgisterUser(string email, string password, string confirmPassword)
{
var client = new HttpClient(new NativeMessageHandler());
client.BaseAddress = new Uri("http://192.168.101.119:8475/");
var postData = new List<KeyValuePair<string, string>>();
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("email", email));
nvc.Add(new KeyValuePair<string, string>("password", password));
nvc.Add(new KeyValuePair<string, string>("confirmPassword",confirmPassword));
var req = new HttpRequestMessage(HttpMethod.Post, "api/Vendor/Register") { Content = new FormUrlEncodedContent(nvc) };
var res = await client.SendAsync(req);
if (res.IsSuccessStatusCode)
{
string result = await res.Content.ReadAsStringAsync();
string test= JsonConvert.DeserializeObject<string>(result);
return test;
}
}
Hope it works for you.

Unable to access REST API’s in Camunda

In our project , we are trying to use camunda BPMN. using camunda standalone distro and deployed and running in Tomcat.
login as a admin user and able to access cockpit and task lists.But,when we try access the APIs using a Java client . we are getting an unauthorized (401) error. Though we are sending JSESSIONID as a “Cookie”
Tried both DefaultHttpClient and HttpURLConnection - It didn’t work out
Note : JSESSIONID is retrieved by calling the login api with admin username and password.
Help me to solve the issue
Attached below is the java client code
public static void main(String[] args) {
CamundaBMPNClient bpmnClient = new CamundaBMPNClient();
Map<Integer, String> cookieHeader = bpmnClient.getCookieHeader();
bpmnClient.getListofTasks(cookieHeader);
}
public Map<Integer, String> getCookieHeader() {
String jSessionID = null;
Map<Integer, String> headerValues = new HashMap<Integer, String>();
HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(
"http://localhost:8090/camunda-webapp-tomcat-standalone-7.2.0/"
+ "api/admin/auth/user/default/login/cockpit");
request.addHeader("content-type", "application/x-www-form-urlencoded");
request.addHeader("Accept", "application/json");
String jsonString = new Gson()
.toJson("username=admin&password=admin#123");
StringEntity params;
try {
params = new StringEntity(jsonString);
request.setEntity(params);
HttpResponse response = httpClient.execute(request);
Header[] cookieheader = response.getHeaders("Set-Cookie");
for (Header s : cookieheader) {
// Do your stuff here
System.out.println(s.getValue());
String[] str = s.getValue().split(";");
int i = 1;
for (String s1 : str) {
headerValues.put(i, s1.trim());
i++;
}
}
System.out.println("jSessionID::" + jSessionID);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return headerValues;
}
public void getListofTasks(Map<Integer, String> cookieHeader) {
int id = 0;
// DefaultHttpClient httpclient = new DefaultHttpClient();
HttpPost request = new HttpPost(
"http://localhost:8090/camunda-webapp-tomcat-standalone-7.2.0/api/engine/engine/default/task");
request.addHeader("Content-type", "application/json");
String[] arrJSessionID = cookieHeader.get(1).split("=");
System.out.println("" + arrJSessionID[1]);
CookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("JSESSIONID=",
arrJSessionID[1]);
cookie.setDomain("http://localhost:8090");
cookie.setPath("/camunda-webapp-tomcat-standalone-7.2.0/");
// cookie.setAttribute(ClientCookie.DOMAIN_ATTR, "true");
cookieStore.addCookie(cookie);
// httpclient.setCookieStore(cookieStore);
HttpClient httpclient = HttpClientBuilder.create()
.setDefaultCookieStore(cookieStore).build();
String jsonString = new Gson().toJson("{}");
StringEntity jsonStr;
try {
jsonStr = new StringEntity(jsonString);
request.setEntity(jsonStr);
HttpResponse response = httpclient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
Header[] header = response.getHeaders("Set-Cookie");
for (Header h : header) {
System.out.println(h.getValue());
}
System.out.println("statusCode::" + statusCode);
} catch (Exception e) {
e.printStackTrace();
}
}

How to call WebAPI method from our Console application?

I need to call webapi from a console application. I have written the
following code but it is not working:
private static async Task UploadDataFromWebApi() {
try {
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
client.BaseAddress = new Uri("http://localhost:8077/api/");
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var resp2 =await client.GetAsync("Report/GetReportGroup");
resp2.EnsureSuccessStatusCode();
var aaa = resp2.Content;
string result = await aaa.ReadAsStringAsync();
Console.WriteLine(result);
Console.ReadLine();
}
catch(Exception ex) {
}
}
But If I am calling the same url that is:
http://localhost:8077/api/Report/GetReportGroup from the Rest client
then it is working fine.
So how can I call the web api methods from my console app?
Thanks in advance.
Below code works for me:-
Console.WriteLine("Making API Call...");
using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }))
{
client.BaseAddress = new Uri("http://localhost:8077/api/");
HttpResponseMessage response = client.GetAsync("Report/GetReportGroup").Result;
response.EnsureSuccessStatusCode();
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("Result: " + result);
}
Console.ReadLine();