HttpClient error calling webapi from another webapi - asp.net-web-api2

I have two webapi developed in asp.net core 2.0 and is secured with identityserver4 framework. Both the apis are ssl enabled
I am accessing the webapi using angular2 application.
Everything works fine when i access the webapi individually from the angular application. But when i try to access the another webapi from one webapi, i am getting
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.Http.WinHttpException: A security error occurred
Below is the code i am using in one of the webapi:
var request = CurrentContext.Request;
var authHeader = request.Headers["Authorization"];
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
var client = new System.Net.Http.HttpClient
{
BaseAddress = new Uri($"https://localhost:5001/")
};
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = authHeaderVal;
var response = await client.GetAsync("api/alerts/1");
if(response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
}
else
{
}

It seams like a CORS issue.
CORS stands for Cross-Origin Resource Sharing.
As the name implies you trying to access resources from a different origin, it could be a different domain, protocol or port.
CORS Requests are enforced by your browser on servers that don't have CORS enabled, check MS docs on how to enable CORS on your api.
The CORS rule is not mandatory to be enforced but most browsers enforce it for security issues, hence some developer tools such as PostMan ignore it to make development easier.
Another solution would be to install a plugin to browser that disables the restriction such as this extension if you're using Chrome.

Most likely you are failing SSL verification since you are running on localhost. You can override verification. Also, HttpClient should be static so you should update the code accordingly.
var request = CurrentContext.Request;
var authHeader = request.Headers["Authorization"];
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (msg, cert, chain, err) => { return true; };
using (var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri($"https://localhost:5001/")
})
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = authHeaderVal;
var response = await client.GetAsync("api/alerts/1");
if(response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
}
else
{
//do something
}
}
}

Related

Connect Appwrite auth with ASP.NET Core 7

I am trying to authenticate an Appwrite user with my ASP.NET Core 7 Web API. In the past, I used Firebase for this with which I was able to implement the function as following:
private static void ConfigureFirebaseAuthentication(IServiceCollection services,
IConfiguration configuration)
{
var options = new AppOptions() { Credential = GoogleCredential.FromFile("firebase-config.json") };
FirebaseApp.Create(options);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
opt =>
{
opt.IncludeErrorDetails = true;
opt.Authority = configuration["FirebaseAuthentication:ValidIssuer"];
opt.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = configuration["FirebaseAuthentication:ValidIssuer"],
ValidAudience = configuration["FirebaseAuthentication:ValidAudience"]
};
}
);
}
This validated the request against the firebase API, but I don't see how I am able to implement something similar for Appwrite. Also the docs don't mention anything helpful.
Does anyone know how to achieve this?
Unfortunately, Appwrite doesn't have a .NET SDK yet so you would have to manually make the API call. I don't know .NET very well, but I generated code using the API specs and Insomnia:
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://[HOSTNAME]/v1/account/sessions/email"),
Headers =
{
{ "X-Appwrite-Project", "[PROJECT ID]" },
},
Content = new StringContent("{\n \"email\": \"[EMAIL]\",\n \"password\": \"[PASSWORD]\"\n}")
{
Headers =
{
ContentType = new MediaTypeHeaderValue("application/json")
}
}
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
If this is successful, you can grab the X-Fallback-Cookies response header and use that for future requests.
Otherwise, if you don't want to create a session server side and you have an Appwrite JWT token generated from your front end, you can make API calls to Appwrite and pass the JWT token in the X-Appwrite-JWT header to make requests on behalf of the user.
For more information on working directly with the Appwrite REST API, refer to the REST docs.

Xamarin.Forms Cookie/User Persistence and ASP.NET Core Authentication/Cookies

Hopefully I'm not misunderstanding something fundamental about ASP.NET Authentication, but here's what I'm trying to accomplish (and failing doing such):
I have a Xamarin.Forms app that needs to be able to go after ASP.NET web services. When I run the App itself, it works fine. The ASP.NET side of it uses Cookie Authentication and the App is capable of getting the Cookie from the Cookie Container -- and then serializing/storing it into secure storage:
For example:
var baseUri = new Uri(url);
baseUri = new Uri($"{baseUri.Scheme}://{baseUri.Host}");
var cookieContainer = new CookieContainer();
/*
authenticationCookie is stored locally and de/serialized via Json from SecureStorage:
var cookie = SecureStorage.GetAsync(AuthenticationCookieKey).Result;
if (!string.IsNullOrEmpty(cookie))
{
authenticationCookie = JsonConvert.DeserializeObject<Cookie>(cookie);
}
*/
cookieContainer.Add(authenticationCookie);
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
{
using (var request = new HttpRequestMessage(HttpMethod.Post, url) { Content = new FormUrlEncodedContent(content) })
{
using (var client = new HttpClient(handler))
{
return client.SendAsync(request).Result;
}
}
}
When I make a Login, I do the following on "success":
SetAuthenticationCookie(cookieContainer.GetCookies(baseUri)[0]);
This sets the local "authenticationCookie" in the class and then serializes it out to SecureStorage.
I've proven/checked that the authenticationCookie is correctly de/serializing and loads up when the Xamarin.Forms app does. I've attached it to my web request. However, when I make the calls, I get a Login request from the opposite end on ASP.NET Core.
The ASP.NET Core server itself works fine normally. If I have a browser instance, it never asks me to login and the browser "remembers" and applies the cookie from login correctly. However, it should seem that the Xamarin.Forms app does not.
What am I missing?
For the sake of argument, this is how I am setting up cookies via the Login method:
//user has a User loaded from EF at this point
var userPrincipal = new ClaimsPrincipal(
new ClaimsIdentity(
new List<Claim>()
{
new Claim(ClaimTypes.Name, user.EmailAddress)
}, "Login"));
var properties = new AuthenticationProperties()
{
IsPersistent = true
};
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, properties);
So, I was successful in using the answer UNDER the selected answer:
How do I set a cookie on HttpClient's HttpRequestMessage
Apparently setting the CookieContainer and then populating it with a Cookie and including that in the Message DID NOT WORK.
The solution was to manually add the Cookie doing the following:
private HttpResponseMessage GetDataResponse(string url)
{
using (var handler = new HttpClientHandler { UseCookies = false })
{
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
{
request.Headers.Add("Cookie", $"{MyCookie.Name}={MyCookie.Value}");
using (var client = new HttpClient(handler))
{
return client.SendAsync(request).Result;
}
}
}
}
This works exactly as we would expect!

Obtaining AzureAD Refresh token in asp.net core 2.2 mvc app

I created a asp.net core 2.2 mvc app with the default template that has authentication through work or school account added to it with the following configuration:
var azureAD = config.GetSection("AzureAD").Get<AzureADSettings>();
if (azureAD.Enabled)
{
services.AddAuthentication(AzureADDefaults.AuthenticationScheme).AddAzureAD(options =>
{
config.Bind("AzureAd", options);
});
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ResponseType = azureAD.ResponseType; //"token id_token"
options.Resource = azureAD.Resource; //"resource link, dynamics in my case"
options.SaveTokens = azureAD.SaveTokens; //"true"
options.Scope.Add(azureAD.Scope); //"offline_access"
});
}
In my controller I get the access token using the following code:
var accessToken = await _httpContext.GetTokenAsync("access_token");
I then use this access token in the ODataLibrary to access data from Dynamics 365 Finance and Operations:
_dataServiceContext.SendingRequest2 += new EventHandler<SendingRequest2EventArgs>(async delegate (object sender, SendingRequest2EventArgs e)
{
var accessToken = await dynamicsToken.GetAccessToken();
e.RequestMessage.SetHeader(OAuthHelper.OAuthHeader, $"Bearer {accessToken}");
});
It worked fine at first but then I started getting 401 Unauthorized status codes and I assume this is because the access token expired so I tried to get a new one using the refresh token:
var accessToken = await _httpContext.GetTokenAsync("access_token");
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);
if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddHours(1))
{
using (var httpClient = new HttpClient())
{
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("client_id", "client id from azure ad"),
new KeyValuePair<string, string>("client_secret", "client secret from azure ad"),
new KeyValuePair<string, string>("resource", "resource link"),
new KeyValuePair<string, string>("scope", "offline_access"),
new KeyValuePair<string, string>("refresh_token", await _httpContext.GetTokenAsync("refresh_token"))
});
var response = await httpClient.PostAsync("https://login.microsoftonline.com/common/oauth2/token", formContent);
response.EnsureSuccessStatusCode();
var newAccessToken = await response.Content.ReadAsStringAsync();
return newAccessToken;
}
}
else
{
return await _httpContext.GetTokenAsync("access_token");
}
But _httpContext.GetTokenAsync("refresh_token") just returns null. Did I not configure it properly or what is the reason I'm not getting a refresh token?
You are using the Implicit grant flow as response type is token id_token .
Your ID token and access token immediately returned to your client app from the authorize endpoint without having to make a second request to the authorize endpoint.
The implicit grant allows the app to get tokens from Microsoft identity platform without performing a backend server credential exchange , which is common used in AngularJS, Ember.js, React.js, and so on . The security characteristics of these apps are significantly different from traditional server-based web applications. Also the implicit grant does not provide refresh tokens.
If using asp.net core , it's recommended to use the authorization code flow . You can find the code sample from:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore
Note : Above sample is for Azure AD v1.0. If you are looking for an Azure AD v2.0 sample (to sign-in users with Work and School accounts and Microsoft Personal accounts, please look at code samples .

How we can authenticate signalR by using cross domain call?

I am working asp.net apis project where i am using signalR and i want to authenticate with authorize attribute.For this i send jwt token and this token add
into signalR pipeline.First i call to Apis to get token.Then i send this token to get signalR connection,But signalR returns unauthorized response.
The below is my code,
app.Map("/signalr", map =>
{
// Setup the CORS middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
// SignalR Auth0 custom configuration.
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
{
OnRequestToken = context =>
{
if (context.Request.Path.Value.StartsWith("/signalr"))
{
string bearerToken = context.Request.Query.Get("access_token");
if (bearerToken != null)
{
string[] authorization = new string[] { "bearer " + bearerToken };
context.Request.Headers.Add("Authorization", authorization);
}
}
return null;
}
}
});
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
app.UseWebApi(config);
GlobalHost.DependencyResolver.Register(
typeof(SignalRHUB),
() => new SignalRHUB(new UnitOfWork(new DbFactory())));
//GlobalHost.HubPipeline.RequireAuthentication();
//app.MapSignalR();

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook