MSGraph Authenticating to dynamics with correct token gives 401 - authentication

Using the following code I can connect to MSGraph (different resource url) and fetch the data that I need, but I'm struggling to realise how to do the same for Microsoft Dynamics (CRM)
public static async Task<string> GetTokenForApplication(string resourceUrl)
{
AuthenticationContext authenticationContext =
new AuthenticationContext(AppModeConstants.AuthString, false);
ClientCredential clientCred = new ClientCredential(AppModeConstants.ClientId, AppModeConstants.ClientSecret);
AuthenticationResult authenticationResult = await authenticationContext
AcquireTokenAsync("resourceUrl", clientCred);
TokenForApplication = authenticationResult.AccessToken;
}
public static async Task CrmTest()
{
var token = await AuthenticationHelper.GetTokenForApplication(#"https://myapp.crm4.dynamics.com/");
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(#"https://myapp.crm4.dynamics.com/api/data/v8.2/accounts");
}
Although I fetch the right token I'll still get a 401: Unauthorized:
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
REQ_ID: 790b38b0-c8c0-4878-a318-e490ee7db57a
Strict-Transport-Security: max-age=31536000; includeSubDomains
Date: Thu, 11 May 2017 15:33:41 GMT
Set-Cookie: crmf5cookie=!UXdAbawdawdal8sNiJ9xy74dAiawdawdDnDsomethingAolDYjkR7innjWIYQ1pV+DcZ9A=;secure; path=/
Server: Microsoft-IIS/8.5
WWW-Authenticate: Bearer authorization_uri=https://login.windows.net/b8faag21-awda-awda-90s4-f8652ss86ddb/oauth2/authorize, resource_id=https://myapp.crm4.dynamics.com/
X-Powered-By: ASP.NET
Content-Length: 49
Content-Type: text/html
}}
A 403 I would understand, but a 401 means that I'm simply using the wrong authentication. Any input on this would be appreciated.
Edit: Is it even possible to access CRM data as an application? There are only delegated permissions available in the azure portal, no application permissions.

While both Dynamics CRM and Graph leverage Azure AD accounts, they have different permission scopes and prerequisites.
To get started, you'll want to follow this Walkthrough: Register a Dynamics 365 app with Azure Active Directory.

Related

xero api fails with unauthorized (401 or 403) after calling auth, refresh and gettenants when calling getinvoices

I'm a rank noob at this, so excuse my ignorance. I've got an MVC web application to login, get the access and refresh tokens, and tenant list OK. I can even get it to refresh the refresh token. No problems.
When I try to run the GetInvoices endpoint either directly or via the sdk, I get 403 (skd) or 401 from the direct api call.
From the latest run with direct call I get this response
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content:
System.Net.Http.HttpConnectionResponseContent, Headers:
{
Server: nginx
Strict-Transport-Security: max-age=31536000
WWW-Authenticate: OAuth Realm="api.xero.com"
Cache-Control: no-store, no-cache, max-age=0
Pragma: no-cache
Date: Wed, 21 Jul 2021 11:19:56 GMT
Connection: close
X-Client-TLS-ver: tls1.2
Content-Type: text/html; charset=utf-8
Content-Length: 95
Expires: Wed, 21 Jul 2021 11:19:56 GMT
}, Trailing Headers:
{
}}
I know that the access token and tenant id used in the GetInvoices step are correct because I checked them against the values pulled in from the auth steps character by character.
The app is being run in Visual Studio 2019, using the self-signed development SSL certificate.
Why is it rejecting the request?
my controllers have the following
private static readonly string Scopes = "openid offline_access profile email accounting.transactions accounting.contacts accounting.attachments";
private static readonly string Scopes = "openid offline_access profile email accounting.transactions accounting.contacts accounting.attachments";
string[] tenant = (string[])TempData.Peek("tenant");
var client = new HttpClient();
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("summaryOnly", "true"),
});
client.DefaultRequestHeaders.Add("Authorization", (string)TempData.Peek("accessToken"));
client.DefaultRequestHeaders.Add("Xero-Tenant-Id", tenant[0]);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var response = await client.PostAsync("https://api.xero.com/api.xro/2.0/Invoices", formContent);
The SDK should handle this for you in the helper methods for the client and OAuth flow but i've included what looks like is missing from just a raw API call below.
Core API call - looks like you need to prefix the token with the Bearer string.
Authorization: "Bearer " + access_token
If you are wanting to use the SDK note that there is a sub Nuget package for OAuth helpers that will help you obtain an access token which you need to pass to core api calls.
https://github.com/XeroAPI/Xero-NetStandard/tree/master/Xero.NetStandard.OAuth2Client
(DOH!) The Tenant returns an Id and a TenantId. I was using the Id.
Thanks to SerKnight and droopsnoot for helping.
I've added code from the OAuth2. The help does not mention to get and cast the return type of RequestAcessTokenAsync.
XeroOAuth2Token authToken = (XeroOAuth2Token)await client.RequestAccessTokenAsync(authorisationCode);
also to check the state on return, you must set in xconfig. mine reads
XeroConfiguration xconfig = new()
{
ClientId = Global.XeroClientID,
ClientSecret = Global.XeroClientSecret,
CallbackUri = new Uri(Global.RedirectURI),
Scope = Global.XeroScopes,
State = Global.XeroState
};
var client = new XeroClient(xconfig);
return Redirect(client.BuildLoginUri());

JWT validation with external authority doesn't work. ASP .NET Core 3.0

I am trying to authorize my requests using external service with IdentityServer4. I use following code
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = Configuration["IdentityUrl"];
options.RequireHttpsMetadata = false;
options.Audience = "myapi";
});
I got my token using token client
var tokenResponse = await tokenClient.RequestPasswordTokenAsync(
new PasswordTokenRequest
{
ClientId = clientId,
ClientSecret = clientSecret,
GrantType = GrantTypes.Password,
Address = discoveryDocument.TokenEndpoint,
UserName = user.UserName,
Password = user.PasswordHash,
});
It works and gives me token, but then when I try to authorize any request by including this token into authorization header it gives me 401 with no explanation. I don't see anything in output of either idenity server application or my client application.
This is what I do in postman to test authorization
Any ideas on what is wrong and how to debug it?
Try to see what the logs says?
Or try to set this one to true in the AddJwtBearer options and then look at the response from the API to see if it contains some additional details about the failure.
options.IncludeErrorDetails = true;
It it works you should see an additional WWW-authenticate header, like this one:
HTTP/1.1 401 Unauthorized
Date: Sun, 02 Aug 2020 11:19:06 GMT
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid"

Login via Postman on a .net core 3 app using Auth0

I have a .net app with login setup via Auth0 (works fine with user interaction) but I want to login with a script with no user interaction, which isn't working. Basically I want to run a LogicApp or some script with hard coded user/pass at night that access a page inside my app.
I've tried Azure LogicApps and Postman with the same results. I try to access a page inside my app by passing user/password as basic auth. I get a few redirects and back to the signin page. Auth0 is no providing any logs, so I assume the authentication is not reaching Auth0.
Obs1: I had this setup that I'm sure was working about 6 months ago, just recently I realized that it had stopped working, could've been a change in Auth0 or due to migrating from .net core 2.1 to 3.
Obs2: I also started testing the login with a Bearer token, but there will be too many changes in the code that I want to leave it as a last resort.
Snippet of Startup.cs
services.Configure<CookiePolicyOptions>(options =>'''
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://login.myapp.net";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = "*********";
options.ClientSecret = "****************;
//Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
//Set the correct name claim type
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "https://schemas.myapp.net"
};
// Set the callback path
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer
options.ClaimsIssuer = "Auth0";
Postman result (3 redirects and a found - login page):
GET https://myapp.net/
302
GET https://myapp.net/Account%2FLogin
302
GET https://login.myapp.net/authorize?client_id=****************&redirect_uri=https%3A%2F%2Fapp.myapp.net%2Fcallback&response_type=code&scope=openid%20profile%20email&code_challenge=***********&code_challenge_method=S256&response_mode=form_post&nonce=*****&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0
302
GET https://login.myapp.net/login?state=***********&protocol=oauth2&redirect_uri=https%3A%2F%2Fapp.myapp.net%2Fcallback&response_type=code&scope=openid%20profile%20email&code_challenge=*********&code_challenge_method=S256&response_mode=form_post&nonce=*************&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0
200
197ms
▼
Request Headers
Authorization: Basic **********
User-Agent: PostmanRuntime/7.22.0
Accept: */*
Cache-Control: no-cache
Postman-Token: **************
Accept-Encoding: gzip, deflate, br
Cookie: did=*********; auth0_compat=***********
Referer: https://login.myapp.net/authorize?client_id=***************&redirect_uri=https%3A%2F%2Fapp.myapp.net%2Fcallback&response_type=code&scope=openid%20profile%20email&code_challenge=***********&code_challenge_method=S256&response_mode=form_post&nonce=******&state=*************&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0
Connection: keep-alive
Response Headers
Server: nginx
Date: Wed, 11 Mar 2020 04:18:29 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
ot-tracer-spanid: 6ed01af3e
ot-tracer-traceid: 3d5f35a407
ot-tracer-sampled: true
X-Auth0-RequestId: 655374d6432978
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1583900310
set-cookie: _csrf=eML8VgsIOn-ONcU0u3TeTx7U; Max-Age=864000; Path=/usernamepassword/login; HttpOnly; Secure
X-Robots-Tag: noindex, nofollow
X-Robots-Tag: noindex, nofollow, nosnippet, noarchive
X-Frame-Options: deny
Content-Security-Policy: frame-ancestors 'none'
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
ETag: W/"a6e-Dn+oh0+jgssgYbnM4PE"
cache-control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0, no-transform
Content-Encoding: gzip
Strict-Transport-Security: max-age=15768000
▼
Response Body
[sign in page]
Please refer to document : Implement the Resource Owner Password Grant
After config your application in portal , you can directly send a post request to Auth0's token endpoint with correct parameters like grant_type/client_id/client_secret/username/password and scope, and token endpoint will return access token which could be used to access the protected resource :
var client = new RestClient("https://YOUR_DOMAIN/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/x-www-form-urlencoded");
request.AddParameter("application/x-www-form-urlencoded", "grant_type=password&username=user%40example.com&password=pwd&audience=YOUR_API_IDENTIFIER&scope=read%3Asample&client_id=%24%7Baccount.clientId%7D&client_secret=YOUR_CLIENT_SECRET", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
You can test with Fiddler or Postman .

Example of asp.net core integration with azure devops via oAuth

I'm trying to use oauth to facilitate integration from an asp.net core app and filing bugs in Azure DevOps. I followed the guide: https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops using the asp.net core 3.0 middleware to configure oauth.
When I hit a page that's been marked with [Authorize], it properly redirects me to the devops auth page with the scopes I've requested, but when I authorize, it redirects me back to my server but has the error:
{"Error":"invalid_client","ErrorDescription":"Invalid client auth token."}
I've confirmed I'm using the right endpoints and the right client secret in my config.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "ado";
})
.AddCookie()
.AddOAuth("ado", options =>
{
options.ClientId = "[AppId from devops]";
options.ClientSecret = "[Client Secret from devops]";
options.CallbackPath = new PathString("/signin-ado");
options.AuthorizationEndpoint = "https://app.vssps.visualstudio.com/oauth2/authorize";
options.TokenEndpoint = "https://app.vssps.visualstudio.com/oauth2/token";
options.Scope.Add("vso.identity");
options.Scope.Add("vso.work_full");
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "UserId");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "EmailAddress", ClaimValueTypes.Email);
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "Name");
});
services.AddControllersWithViews();
Configure:
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
The examples I found using oAuth with devops are from Asp.Net webforms, are there any for asp.net core?
(This is the full error)
An unhandled exception occurred while processing the request.
Exception: OAuth token endpoint failure: Status: BadRequest;Headers: Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
P3P: CP="CAO DSP COR ADMa DEV CONo TELo CUR PSA PSD TAI IVDo OUR SAMi BUS DEM NAV STA UNI COM INT PHY ONL FIN PUR LOC CNT"
Set-Cookie: VstsSession=%7B%22PersistentSessionId%22%3A%22f8e30b87-a6eb-470d-9ea2-ddf7b1f0dd84%22%2C%22PendingAuthenticationSessionId%22%3A%2200000000-0000-0000-0000-000000000000%22%2C%22CurrentAuthenticationSessionId%22%3A%2200000000-0000-0000-0000-000000000000%22%7D; domain=.visualstudio.com; expires=Mon, 16-Sep-2024 22:38:25 GMT; path=/; secure; HttpOnly
X-TFS-ProcessId: 98486e68-ccc8-4bc2-9907-f44cec26922a
Strict-Transport-Security: max-age=31536000; includeSubDomains
ActivityId: b0088e1b-d2d0-4788-8328-d97aeeecb447
X-TFS-Session: b0088e1b-d2d0-4788-8328-d97aeeecb447
X-VSS-E2EID: b0088e1b-d2d0-4788-8328-d97aeeecb447
Request-Context: appId=cid-v1:20b3930f-73dc-453a-b660-e3891d782eef
Access-Control-Expose-Headers: Request-Context
X-Content-Type-Options: nosniff
X-MSEdge-Ref: Ref A: 9DC5A709B96D4D838858E4FC56797DE4 Ref B: WSTEDGE1017 Ref C: 2019-09-18T22:38:25Z
Date: Wed, 18 Sep 2019 22:38:24 GMT
;Body: {"Error":"invalid_client","ErrorDescription":"Invalid client auth token."};
Unknown location
Exception: An error was encountered while handling the remote login.
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
If your Post for the authorization code was done via url encoded query string, You can try getting it done via the request body instead. You can refer to a sample here.
The issue is that the parameters used in the OAuthHandler to exchange the authorization code for a token are different than those used in the Azure devops auth sample.
https://github.com/aspnet/AspNetCore/blob/master/src/Security/Authentication/OAuth/src/OAuthHandler.cs, line 179
https://github.com/microsoft/azure-devops-auth-samples/blob/master/OAuthWebSample/OAuthWebSample/Controllers/OAuthController.cs, line 74
You can work around this by creating your own handler that inherits from OAuthHandler and overrides the ExchangeCodeAsync method to use the paremeters from the sample.

How to get id_token from TokenEndpoint of IdentityServer4 through authorization_code flow?

I would like to get "access_token" and "id_token" from Token endpoint through Authorization Code flow. But, I am getting "invalid_grant" error while calling the token endpoint with following parameters on postman.
POST /connect/token HTTP/1.1
Host: localhost:2000
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: a8a29659-0ea3-e7dc-3bd6-6e6630a7370d
client_id=client
&client_secret=client
&grant_type=authorization_code
&username=admin
&password=admin
&scope=openid+profile
Client Configuration:
new Client
{
ClientId = "client",
ClientSecrets =
{
new Secret("client".Sha256())
},
AllowedGrantTypes = new List<string> { OidcConstants.GrantTypes.AuthorizationCode },
AllowedScopes = {
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
}
}
What is wrong in my client configuration section? and, How do i make a successful post request to Token Endpoint?
The authorization code grant type requires a code parameter to be sent during the token request (see RFC6749 section 4.1.3).
This code is issued by the authorization server after the resource owner authorized the client (see RFC6749 section 4.1.2).