I've got an existing Web API backend that uses OAuth to authenticate a vue.js frontend call's. This is an existing one and I can't modify it.
I need to perform the authentication from a new WPF Application I wrote.
I've composed the query using the HttpClient in the form
http://backend/api/signin?grant_type=password&username=user&password=1234hola
but I receive an error regarding the grant_type. Is there a tutorial I can follow? I didn't think it was that difficult to perform the authentication, but I think I'm missing something really stupid
Thanks in advance
You should add the credentials to the header as suggested here:
var client = new HttpClient() { BaseAddress = new Uri("http://url.com") };
var request = new HttpRequestMessage(HttpMethod.Post, "/path");
var byteArray = new UTF8Encoding().GetBytes("<clientid>:<clientsecret>");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
...
var response = await client.SendAsync(request);
Related
I attempted to build this application myself but, have hit several stumbling blocks along the way. I am thinking that it may be best to step back and take a larger look at what I am trying to create. There doesn't seem to be any documentation on how to make what I am looking for. (unless someone can point me in the right place I might have missed)
Ultimately what I would like is to have a Blazor(server-side) application make API calls to use data in the app and then have an IdentityServer4 encapsulate the authentication. I need to have Azure as well as ASP.net Identity as the possible authentication methods.
I have tried and was able to create an IdentityServer4 that also has a local API. I can make calls to this from Postman to get token and such. But, when it comes to tying a Blazor(server-side) application to the IdentityServer4 I am befuddled.
I have tried to ask this question in specifics but, haven't gotten any results at all. I am hoping maybe this larger look at it might be helpful.
It seems like odic-client.js is the way to get the data from the IdentityServer4 callback but, that doesn't seem to tie in nicely with the .NET Authorization in Blazor(server-side). How do I get these to work together.
IMPORTANT: There are better sources now than my answer. Follow the links provided in the last part of this answer.
I've got a similar setup with API / IdentityServer4 / Blazor(server-side). I'll show you some of the code I used, maybe you can make some use of it.
Using the NuGet Package Microsoft.AspNetCore.Authentication.OpenIdConnect, I've got this code in the ConfigureServices method in the Startup class:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "myClient";
options.ClientSecret = "mySecret";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("MyApi");
options.Scope.Add("offline_access");
options.ClaimActions.MapJsonKey("website", "website");
});
and in the Configure method app.UseAuthentication();
Then in App.razor i used the CascadingAuthenticationState component:
<CascadingAuthenticationState>
<Router AppAssembly="typeof(Startup).Assembly" />
</CascadingAuthenticationState>
And using the NuGet package Microsoft.AspNetCore.Authorization in my main page Index.razor:
#using Microsoft.AspNetCore.Authorization
#attribute [Authorize]
Now it should say "Not authenticated" when you open the main page but there's still no redirection to the IdentityServer4. For this you've got to add MVC in the startup too, as I learned from this stackoverflow question:
services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Now you should be getting redirected to IdentityServer4 to log in after starting the application. In my case I've got an ApiClient, which describes the methods of my API. I use DI to inject the ApiClient and add the access token:
services.AddHttpClient<IApiClient, ApiClient>(async (serviceProvider, client) =>
{
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
client.BaseAddress = new Uri("http://localhost:55578");
});
Like you said, there is not much documentation on this topic except some answers here on stackoverflow. It took me a long time to set this up, so I hope I can help someone else with this post.
UPDATE: Logout process
Logging out with this setup requires a detour to a razor page because the HttpContext is inaccessible after the blazor component is loaded.
Create a new Razor Page in the Pages folder and add the following code to the newly created Logout.cshtml.cs:
public class LogoutModel : PageModel
{
public async void OnGetAsync()
{
await HttpContext.SignOutAsync("Cookies");
var prop = new AuthenticationProperties()
{
RedirectUri = "http://localhost:62909"
};
await HttpContext.SignOutAsync("oidc", prop);
}
}
Add a logout button somewhere which calls the function UriHelper.NavigateTo("/Logout") relying on #inject IUriHelper UriHelper. Done!
UPDATE: Login Workaround
The previously described login process worked locally but after publishing to the test server, I had the problem, that the IHttpContextAccessor was always null inside the AddHttpClient method. So I ended up using the same workaround as with the logout process. I let the IdentityServer redirect to a razor page (which always has a HttpContext), save the access token in the user claim and redirect to the index page. In the AddHttpClient method I only get the token from the user claim and put it into the authentication header.
UPDATE: Open issues
I still struggle to get this setup working on our server. I opened this issue and requirement on the AspNetCore Github but both got closed without a proper answer. For the time being, I found some blogs that give a good overview of the current state of the topic:
https://mcguirev10.com/2019/12/15/blazor-authentication-with-openid-connect.html
https://wellsb.com/csharp/aspnet/blazor-consume-identityserver4-protected-api/
Try this
Blazor Consume IdentityServer4 Protected API
I have been playing around with Managed Service Identity in Azure Logic Apps and Azure Function Apps. I think it is the best thing since sliced bread and am trying to enable various scenarios, one of which is using the MSI to get an app-only token and call into SharePoint Online.
Using Logic Apps, I generated a managed service identity for my app, and granted it Sites.readwrite.All on the SharePoint application. When then using the HTTP action I was able to call REST endpoints while using Managed Service Identity as Authentication and using https://.sharepoint.com as the audience.
I then though I'd take it a step further and create a function app and follow the same pattern. I created the app, generated the MSI, added it the Sites.readwrite.All role same way I did with the Logic App.
I then used the code below to retrieve an access token and try and generate a clientcontext:
#r "Newtonsoft.Json"
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.SharePoint.Client;
public static void Run(string input, TraceWriter log)
{
string resource = "https://<tenant>.sharepoint.com";
string apiversion = "2017-09-01";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET"));
var response = client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}", Environment.GetEnvironmentVariable("MSI_ENDPOINT"), resource, apiversion)).Result;
var responseContent = response.Content;
string responseString = responseContent.ReadAsStringAsync().Result.ToString();
var json = JsonConvert.DeserializeObject<dynamic>(responseString);
string accesstoken = json.access_token.ToString()
ClientContext ctx = new ClientContext("<siteurl>");
ctx.AuthenticationMode = ClientAuthenticationMode.Anonymous;
ctx.FormDigestHandlingEnabled = false;
ctx.ExecutingWebRequest += delegate (object sender, WebRequestEventArgs e){
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + accesstoken;
};
Web web = ctx.Web;
ctx.Load(web);
ctx.ExecuteQuery();
log.Info(web.Id.ToString());
}
}
The bearer token is generated, but requests fail with a 401 access denied (reason="There has been an error authenticating the request.";category="invalid_client")
I have tried to change the audience to 00000003-0000-0ff1-ce00-000000000000/.sharepoint.com#" but that gives a different 401 error, basically stating it cannot validate the audience uri. ("error_description":"Exception of type 'Microsoft.IdentityModel.Tokens.AudienceUriValidationFailedException' was thrown.). I have also replace the CSOM call with a REST call mimicking the same call I did using the Logic App.
My understanding of oauth 2 is not good enough to understand why I'm running into an issue and where to look next.
Why is the Logic App call using the HTTP action working, and why is the Function App not working??
Anyone?
Pretty basic question: Have you tried putting an '/' at the end of the resource string e.g. https://[tenant].sharepoint.com/ ?
This issue is also present in other endpoints so it may be that its interfering here as well.
I've successfully implemented this tutorial and have a client + server working locally.
However, the front-end application that I'm building is an Angular app - this means that it isn't possible to store a client secret in it..
Relevant code:
ConfidentialClientApplication cca = new ConfidentialClientApplication(Startup.ClientId, Startup.Authority, Startup.RedirectUri, new ClientCredential(Startup.ClientSecret), userTokenCache, null);
var user = cca.Users.FirstOrDefault();
AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, user, Startup.Authority, false);
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, apiEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
How can I set my frontend up securely to work without having a client secret, based on the tutorial mentioned?
I'm currently using this angular library .
You can achieve this by using implicit grant flow
You can seamlessly integrate into any SPA application.
Follow https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-reference-spa, this article helps you.
There is an SPA sample already available in GitHub, you can try https://github.com/Azure-Samples/active-directory-b2c-javascript-hellojs-singlepageapp
you can use oidc-client library instead hello.js in above sample, both are very similar and easy to implement.
I need to form a POST to publish a Google PubSub message. I can't use the client libraries because they use gRPC which is incompatible with Google App Engine. I can form the critical POST request, but I'm not sure how to authenticate it using OAuth2.
This link shows what I'm doing, but it obscures the authentication part.
https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/publish
(If GAE standard environment would support gRPC this would not matter.)
JSONObject obj = new JSONObject();
JSONArray attr = new JSONArray();
obj.put("script_name","foo_script.py");
obj.put("script_args","arg1");
attr.put(obj);
JSONObject jsontop = new JSONObject();
jsontop.put("messages",attr);
URL url = new URL("https://pubsub.googleapis.com/v1/projects/{my-URL}/topics/topic_run_script:publish");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
This code comes back "401 : UNAUTHENTICATED". How do I authenticate it?
App Engine has an API to fetch an access token that you can use to when calling Google services. For documentation and an example, see https://cloud.google.com/appengine/docs/standard/java/appidentity/#asserting_identity_to_google_apis
You might also be able to use the pubsub client library on GAE Std if you switch to the Java 8 environment. This doc implies that it should work.
using this sample:
https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect
This works as expected when running it locally
But when we deploy it (azure web app), it still authenticates, but the OpenIdConnectAuthenticationNotifications.AuthorizationCodeReceived event is not firing.
This the code.
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = redirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed
}
});
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
Uri uri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(code, uri, credential, graphResourceId);
}
This is a problem because it requires caching of the token to make an outbound call.
Since it doesn’t have it, it throws.
There was an issue that caused this related to a trailing slash after the redir url but we’ve already tried that.
So two questions…
1) Under what conditions would event get fired and why would this work when running locally? According to the docs it should be "Invoked after security token validation if an authorization code is present in the protocol message."
2) What is the best way to debug this? Not clear on what to look for here.
1) Under what conditions would event get fired and why would this work when running locally? According to the docs it should be "Invoked after security token validation if an authorization code is present in the protocol message."
As the document point, this event will fire when the web app verify the authorization code is present in the protocol message.
2) What is the best way to debug this? Not clear on what to look for here.
There are many reason may cause the exception when you call the request with the access_token. For example, based on the code you were using the NaiveSessionCache which persist the token using the Sesstion object. It means that you may also get the exception when you deploy the web app with multiple instance. To trouble shoot this issue, I suggest that you remote debug the project to find the root cause. For remote debug, you can refer the document below:
Introduction to Remote Debugging on Azure Web Sites