How to authenticate an HttpClient connection with an external provider (Google) - c#

Edit:
Here is my question reformulated:
I have a web server with secured api endpoints - one must have been authenticated with Google prior to using them. I implemented Challenge and Callback endpoints for that.
This works well from a browser with my SPA web front-end. The user gets redirected to the Google website to sign-in and then gets redirected back to my webapp; the browser then has the authenticated cookies and the webapp can use the endpoints to update its state.
I also have a WPF application that will communicate with the web server.
I want the WPF application to do the same as the web front-end: Use the web api endpoints after being authenticated with Google. The connection between the WPF application and my web server is done through an HttpClient.
My problem is I don't know how to authenticate that HttpClient connection between the WPF app and the web server.
I tried using the same Challenge endpoint but the response I get is of course the HTML from the Google Sign-In page, so I guess I can't use that with an HttpClient...
I also tried authenticating with GoogleApis from the WPF app and use the authenticated token to set cookies in the HttpClient but apparently this is not compatible.
How to authenticate an HttpClient connection to a web api with an external provider such as Google?
Original question:
From a WPF application, the user authenticates with Google with this code:
using Google.Apis.Auth.OAuth2;
...
public void Authenticate()
{
UserCredential credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "myClientId",
ClientSecret = "myClientSecret"
},
new[] { "email", "openid" },
"user",
CancellationToken.None).Result;
}
This works and the UserCredential object contains the authenticated token:
How to embed this token information in a web request made with an HttpClient in order to call my webapi endpoint?
I think the request must include some cookies to inform the server that it has been authenticated, but I don't know which ones exactly.
The endpoint on the server-side validates that the user is authenticated with the help of IdentityServer:
var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("External authentication error");
}

If I got your question right, you just have to set the Authorization header
var credentials = await GoogleWebAuthorizationBroker.AuthorizeAsync(
clientSecrets,
new[] { "email", "openid" },
"user",
CancellationToken.None);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
credentials.Token.TokenType,
credentials.Token.IdToken);

Maybe you'll find below a helpful hint to better understand OpenID :)
The confusion stems from mixing GoogleApis and IdentityServer frameworks.
Authentication/authorization can be achieved using either of them.
Objects from Google.Apis.Auth.OAuth2 and IdentityServer4 namespaces are not designed to interact.
No manual cookie handling is necessary, for sure.
Ask yourself to whom does Google provide trust for the user. If it calls back to WPF, then webapi trusting WPF is a separate issue.

You answer your own question in the question:
the browser then has the authenticated cookies and the webapp can use
the endpoints to update its state
HttpClient needs to send those same cookies.
How do I set a cookie on HttpClient's HttpRequestMessage

If I understood your question right, then I faced the same problem not too long ago.
The way I implemented it is that in the backend, no matter who tries to access the endpoint, they had to send a Bearer X authorization token.
The token contained the identity of the client that wanted to access the resource, and I checked if he was permitted.
No matter what kind of client wants to access the endpoint, it just has to have that authroziation header in the request that he sends and the backend will treat it the same.
In my scenario, I used an authentication service that returns a cookie to the client with a certain JWT that contains the identity information.
Then from the client I send in every request the JWT received from the authentication service as an authorization header to the backend.
The reason I had to put the JWT that I receive from the service in a header, is that the authentication service and the backend service are not in the same domain, so cookies cant be shared.
This results in such design that no matter how you authenticate the client, the end result must be some sort of token that the backend can receive and read.
Hope this helps.

Related

Single sign on using custom API

I want to implement a single sign on feature using my own API. Third party application(web application) will call this API and authenticate the users. For the communication between my API and other applications web request will be used. Below is the solution I provided for this,
I have created a API on my application and do the authentication based on request values. After successful authentication, I create the authentication cookie and add it to the response.
On the other app I used a HttpWebRequest and create CookieContainer. Then I get the cookies from response and assign those cookies to Response.
var response = (HttpWebResponse)http.GetResponse();
foreach (Cookie cook in response.Cookies)
{
Response.Cookies.Add(new System.Web.HttpCookie(cook.Name, cook.Value)
{
Domain = cook.Domain,
Expires = cook.Expires
});
}
In my test environment this works fine since both authentication API and other app are in same domain. But in customer testing phase this does not work due to domain mismatch. Because Authentication API is in different domain.
Is there any way to resolve this issue ?
I think it is impossible with cookies because they are domain bound and are not sent along with requests to domains. I guess you need see about other technology.
I hope this link will be useful
Good Luck

Dynamics CRM api HttpClient Request Authentication with ADFS 3.0

I have an on-premise Dynamics CRM (2016) that is configured with ADFS (3.0). When a user want's to Login, they get redirected to the ADFS login page and the user enter their Windows AD credentials.
From a .net core application I need to make request to the CRM api using HttpClient. When I try to send the credentials like I normally would for a Windows Auth CRM it doesnt work. I get a 401 Unauthorized. Like below.
HttpClient client = new HttpClient(new HttpClientHandler() { Credentials = new NetworkCredential("myuser", "mypassword", "mydomain") });
var result = client.GetAsync("https://mycrmaddress/api/data/v8.0/accounts");
I also tried using Adal to retrieve a token and attach it as a bearer token to the request but I'm unable to get a token with adal. When I try I receive the following:
The authorization server does not support the requested 'grant_type'. The authorization server only supports 'authorization_code'
ADFS 3.0 doesn't support this flow.
I cannot upgrade to ADFS 4.0 so I would like to know what are my options to make an authenticated call to CRM api (without prompting a login window as this application is a service).
Is there any configuration I can do on ADFS so my first example work? Or is it possible to do it with Adal even if it's ADFS 3.0? Or any other solution...
I found the answer to my question. It's kinda hackish, but I tested it myself and it works. As a temporary solution this will do the trick.
Details are available here: https://community.dynamics.com/crm/f/117/t/255985
ADFS 3.0 supports the Authorization Code flow and this what we will use in this case.
We need to retrieve an authorization code. Normally at this steps a windows is prompted to the user to enter its credentials. By doing a POST and sending the user/password it's possible to retrieve an authorization code.
{authProvider} - ADFS Uri - something like
https://adfs.mycompany.com/adfs/oauth2/
{ClientId} - The Guid used to
by your infrastructure team to add your application to ADFS
{RedirectUri} - The IFD Uri for dynamics - should match the redirect
Url used to by your infrastructure team to add your application to
ADFS
username - The User set up on ADFS and in Dynamics
password - The password for the above user
Then we make the following call with these information using HttpClient.
var uri = $"{authProvider}authorize?response_type=code&client_id={clientId}&resource={redirectUri}&redirect_uri={redirectUri}";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("username",username),
new KeyValuePair<string,string>("password",password),
});
var responseResult = _httpManager.PostAsync(uri, content).Result;
The response content will be an html page (Remember normally this flow prompts a login page to the user). In this page there will be a form that contains the authorization code. using a library like HtmlAgilityPack retrieve the token. This is the hackish part of the solution.
Now that we have an authorization code we need to retrieve an access token.
For that we need to make the following call
var uri = $"{authProvider}token";
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","authorization_code"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("redirect_uri",redirectUri),
new KeyValuePair<string,string>("code",code)
});
var response = await _httpManager.PostAsync(uri, content);
The response content will be a json string that will contain the access token.
With the access token, make the call to CRM rest API.
You will need to attach the token to the HttpClient in the header as a bearer token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",token);
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
From now on you can make calls to CRM api and you will be authorized. However be carefull normally access token are short lived. You will either need to increase their lifetime or request a new token everytime it's expired.

Calling Azure AD secured web API with client / secret

I've been at this whole day and it's driving me NUTS. I've got a web API published on Azure which is secured with Azure AD authentication. I've been using this API in combination with a mobile app for quite a while now. On the mobile app, I use the library for client authentication (Microsoft account which is in AD) and this works perfectly. Within the app, API requests are authenticated with the X-ZUMO-AUTH header.
Now for a new part of the application, I've got a C# console app which also needs to be able to call the app. This needs to run stand-alone, so without asking for user credentials. So I created an application in Azure AD, gave it permissions to the Web API instance and got me some code to get an authentication token. That token is then passed into the HttpClient object:
AuthenticationContext ac = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(clientID, clientSecret);
AuthenticationResult authenticationResult = await ac.AcquireTokenAsync(resource, clientCred);
string authToken = authenticationResult.AccessToken;
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
HttpResponseMessage response = await client.SendAsync(request);
Server auth is set-up like this:
string aadTenant = configProvider.GetConfigurationSettingValue("ida.Tenant");
string aadAudience = configProvider.GetConfigurationSettingValue("ida.Audience");
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = aadTenant,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = aadAudience
},
});
I have triple checked all the variables. I also verified that the Bearer token is correctly passed to the service (Fiddler shows it). Everything seems absolutely fine, but still, the server keeps responding with Unauthorized.
My hunch is that this has something to do with the Web API. It's been set up as mobile services back-end. I'm suspecting that this is somehow interfering with 'normal' authentication schemes and the 401 is coming from mobile services not understanding this authentication request, even though the bearer authentication has been setup. But this is just a hunch and I'm not sure whether it actually makes sense or not.
I've seen the related questions on this topic, and all of the samples (used this one as the primary source of inspiration). I've tried all of the variations out there, to no avail. So what I'm looking for is ways to find out where the actual problem is. "Unauthorized" is a bit vague to say the least, so I'm trying to find out why the calls are returned with this response. Any help is greatly appreciated.
Additional endpoint info
As requested, here's some info on the endpoints I'm using:
authority = https://login.microsoftonline.com/{aad-tenant-name}
Also tried the OAuth2 token endpoints listed on the management page:
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/token
authority = https://login.microsoftonline.com/{aad-guid}/oauth2/authorize
For the client, information I'm passing in the ClientID and generated secret I got from the AAD Application page for the application I created. I have granted the application permissions to access the App Service instance for my web API endpoint. Those calls are going to:
https://{app service url}.azurewebsites.net/api/controller
Whilst trying to get this going I noticed more weird behavior. I thought for now I'd just set this particular controller to [AllowAnonymous] so that I could call it without any authentication. But when I do so and omit passing in the Bearer token, I still get Unauthorized as response. That kind of enforces my idea that there's something wrong on the server side and that this might have something to do with how the mobile app stuff is strapped to the webapi controllers.
Never mind, the controller being forced to authorize was caused by Web App level authentication settings, documented here

Can I use Chrome Web Store payments with OAuth 2.0

I've written a hosted Chrome Web App which authenticates the user with OAuth 2.0 using the Google APIs Client Library for .NET. Now I want to add payments to our application using the in-built Chrome Web Store Payments.
Looking at the documentation it appears that I need an OpenID URL in order to check for payment.
How can I get this UserID/OpenID URL since I'm using OAuth instead of OpenID authentication?
var service = new Google.Apis.Oauth2.v2.Oauth2Service(
new BaseClientService.Initializer
{
HttpClientInitializer = userCredential,
ApplicationName = "My App Name",
}
);
HttpResponseMessage message = await service.HttpClient.GetAsync(
string.Format("https://www.googleapis.com/chromewebstore/v1/licenses/{0}/{1}",
appId,
fedId // Where do I get this??
)
);
I wanted to leave my own experience here so that others can see that it's not just a matter of using the existing Id returned from an authorised request to the user profile endpoint, as this is not the ID required by the Chrome Payments API...
Short Answer
It's not possible to use only OAuth2.0 for a hosted app. The only options for a hosted app are:
Use the deprecated OpenID (see detailed answer)
Use In-app Payments using Google Wallet for Digital Goods
Long Answer
We still have to use OpenID, however Google has provided a migration path for OpenID users to OAuth2.0 called OpenID Connect. The aim of this migration is to map the old fedId field to new the Google+ User Ids.
This allows us to retrieve an OpenID identifier using an existing OAuth 2.0 process.
Caveat: The Google .NET Client APIs do not support this migration path. So authentication must be done manually or using a 3rd party OAuth library.
Howto:
As per usual OAuth flow, direct the user to the Authenticate endpoint (https://accounts.google.com/o/oauth2/auth) with the following variables:
openid.realm=http://localhost ** Required, where http://localhost matches your redirect_uri variable
scope=openid profile https://www.googleapis.com/auth/chromewebstore.readonly ** Both openid and profile scopes are required in order to retrieve the OpenID identifier. The chromewebstore scope is required to query the payments API.
Then exchange the code for an access token from the Token endpoint (https://accounts.google.com/o/oauth2/token)
At this point you will receive the standard access_token, refresh_token, etc variables but also an additional id_token variable.
This id_token is a JWT-encoded string containing the OpenID information.
Decoding this JWT-encoded (you can use this C# JWT Library) string will give you a JSON string in the following format:
{
"aud": "<googleuserid>.apps.googleusercontent.com",
"at_hash": "<hashcode>",
"iss": "accounts.google.com",
"openid_id": "<!! The fedId we require !!>",
"exp": <id>,
"azp": "<googleuserid>.apps.googleusercontent.com",
"iat": <id>,
"sub": "<googleuserid>"
}
At this stage we've finally found what we're looking for, the openid_id. This can be used to communicate with the Chrome Payments API
While still using the same OAuth credentials, make a signed request to the following URL:
https://www.googleapis.com/chromewebstore/v1/licenses/{appId}/{openId}
{appId} is the ID of your app within the Chrome Web Store
{openId} is the openid_id from the JWT response
This should give you what you need:
https://developers.google.com/accounts/docs/OAuth2
Its a complete overview of OAuth2.0.
Helped me with a problem I was having with a webapp setup, hope it can do the same.
P.S - Im not sure but this may be exactly what your looking for:
https://developers.google.com/accounts/docs/OAuth2InstalledApp

Access token from WP8 not working on WebApi 2 endpoint

I have a basic SPA template with WebApi 2 backend that uses Microsoft account as external login provider. I also have a WP8 client in which users can login with their Microsoft account, both the SPA and WP8 client use ClientId so a users with same email address get the same identifiers in both clients (the ProviderKeys stored at the db match the id's for the same users when they log in on the phone).
When a user has logged in through the browser I can get their access_token and authenticate further requests they make against the api through the bearer token, this works well, returning 200 every time. On the WP8 client it doesnt work however, this is how I get the token:
LiveAuthClient Auth = new LiveAuthClient("0000000041422E16");
//some code removed
LiveConnectClient connect = new LiveConnectClient(Auth.Session);
string access_token = connect.Session.AccessToken;
//use token to send a http request to an authorized endpoint
I've used Fiddler to test the access token and the WebApi always return 401, what am I doing wrong here? Aren't the tokens sent back by the Live SDK client valid for authentication on the WebApi endpoints?

Categories

Resources