Bad Request when trying to login with an invalid password for user in Web Api 2 - asp.net-web-api2

I developed a login system using Web Api 2..... all worked perfectly, but suddenly it stopped working when user enters wrong password at login screen.
When user and password are correct, the token is correctly gotten and user is logged in.
But when user or password is wrong, it is getting a Bad Request response. This worked before, so i don't know what happened.
When debugging, I could see that OAuthAuthorizationServerProvider set the invalid_grant in such a case, in this code:
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "El nombre de usuario o la contraseña no son correctos.");
return;
}
if (user.Locked)
{
context.SetError("invalid_grant", "El usuario está bloqueado. Contáctese con el administrador.");
return;
}
Unfortunately, I cannot debug further, so I could not know what happens after the "return".
Any idea what is happening?
These are the headers of the response (url is /Token)
Cache-Control
private
Content-Length
4901
Content-Type
text/html; charset=utf-8
Date
Fri, 30 Nov 2018 23:44:15 GMT
Expires
-1
Pragma
no-cache
Server
Microsoft-IIS/10.0
X-Powered-By
ASP.NET
X-SourceFiles
=?UTF-8?B?QzpcV29ya2luZ0ZvbGRl…Edlc3RvckRvY1dlYlxUb2tlbg==?=

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());

How to fix SLO with passport-saml that works on first logout but not on subsequent logouts

I have to connect an application to my company's ADFS server. I am using passport-saml for SSO and SLO. SSO works, and SLO works on the first logout only. I am trying to make SLO work every time a user logs out.
I have been searching high and low for a solution to this problem, but it evades me. Here's the details:
I clear the cookies in the browser to start with a clean slate.
I login to my application which redirects to ADFS' login page
Enter user credentials and then ADFS redirects back to my app's homepage
I log out of my app and a request is sent to the ADFS server killing my session locally and on ADFS, I am then redirected back to my app's homepage
I log in again and this works as intended
I logout but this time I am sent to my ADFS server's logout page.
Further inspection shows that ADFS is not clearing its cookies so the ADFS session stays live.
I have used Firefox's SAML viewer plugin to watch what is happening and here are my findings:
On a successful logout:
HTTP:
GET https://myadfs.org/adfs/ls/?wa=wsignout1.0 HTTP/1.1
Host: myadfs.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://example.com/dashboard/data
Connection: keep-alive
Cookie: MSISAuth=AAEAAMVBaN7qo03wm/4jDH9e/tZ6ih6HN++2S7c7c0aXHK1RYIZ5++4Y7pf3g4v+OdRUzcJgOROfZkXx0tSEeCOfJFMluodJiSYsESiJnidVcR7Os/iHkNqIp88qGG7UZj+l8NYyvsO/7soTyQGkbMqoI0Z+0z+xXz2CZgOxsqWcjJ3FmTR32bsMR8Lra77XI2KyKycFiNYdYJ2dSKC7yBdxBRKHB7LAs4DOJKAtOt//IWspe9zPbju+x6chgP0dKToyfqX6m4EwlQnbHG4hmCImtXrEDytx1rbuLiBC7N56Y9WmGBTht5vgYvVEoA2cRqBbNYK+HoonL6+oBIJdba6+XZ2lBQsO/yJowvaHxPM8wgwLBknSt39RswaSdGjrI18CcgABAAB/eeLBPuQ9dk6ItCeTem38XttX/PQPLi52Ts+ZQGYHxs4VsO1EMe7EgMGYThPGlMCDcmS9ouXOSh6yW/LiL1jTuhc2/jhq3X0jWY+XPOSXtp81mineHeNv8SWsFjggzh5AymLtPPrPUYT6ihj9fcbJymqatsZMI5B5h0gxS2LaUUWjJyRxpMIyQXEpLSx1mxU5psQrj5/nGpOiq98uy8HE4kJp+Ey9uugSZQXhn9NwY+EqqmWxf6LDrCaeMLFDIX6mlgqu2eTLrUA9gNIJ4kSOC/5Rtw4JQVJpSeQuMom6kCHFEvZo/57BIhGkgWR8vNNCguHzZeB+as0xxfxmmb9SgAMAAMVFqaMXn0uG8+IGJIfxdIIoJ7EsLqV7so7WnFT/4OxfLzsXlO2flq0vcEbasLuLoqhGFaOuy1dkq/ft9se6Pv6rQfH7Esk/aMey/cKObBUPkcZAUFtQxXD7MSLScsiVnq3hHjrpZzEnMTToVkA9Zjv3i72Wv20tdE658+7O1olibavPPIT7Z5syoQNa1rjOAaXcPlM5hbbjXm7BiXx37ZEnvxwpY1Mf4Yocvgd9kMoApciDB2csbTf4GEic7MKeAI2G5KpwArY7g+zt4BJud+F/xnyuwVPpwPVEiNbHQnAogh5NoMDwRx+macTdkHku4AdNvruS/4L/aUHcEhPlhu3j/7r9kP1EnRso12NP1AWipsGlmpdAjoIXfK0+NBqJnDq0KwSEcvJ38OI6Z1FVkRWySi8br8pjtcytFhdh5RTkpD8FVQZ/RnGC1XE4q4IJhxMBlE1Kd8PNh3p85qpoX6r2I36a3knwK2dkm7pb0XNVwhxhC5DGpaB2iNo86CGi+BX4rICBGkNgyrOW/aWKpIhLu0bo1IDVQJw7MORdROJJk/o81E15HuC2g4r3ch+IvZOXKfAenGYM2mYrgnSRHLD0p7KsDN0vuU3IdLXAL5/D5ezr3WQFDFXPpRJyQ+qfx8kyUCe/vtvEVaNezHzOKosQsNGwSvp+lHrEGA9LLYM8RkU/Vwshgkeq2H8MoyuDRaxgOoudNGOmvwNfMp9BoOsz8OCDA5R2BB+JXzsEkSpNYebJK+VWm5wOcYnJ2j9y1OKjRU1ICRtsSPG5kLWmYUt8hHsswzrj4UAxpks+Dn2S09YzeOudC5ss5hmTM/UeVG3r3kJ9+Ad7716V9g7016u+XGhfSWty8EPxVAg0qV9wwAIk+FliWFdF1OLY1RODcsS3swqYfMrBWWdULVNl5d36ycFGucaP893o4Q/im7tx2+588lfvPbZO+DkP40MHP9Hwe++ra6kDiQx5si4M16zYIMmxa4nq6XVcr2hFlqbsLQjhIqkiFOCkt9LNRdKNZlghQkspUH44qLBq4sTHK0iD13FFmBs5rEE1CWa89oCELhea/Z9hPEtjPpC3Q52cAXBgbOJCTr6OYFYfQKbATqHdTU09/nJOafMK5ID1pf7pmBL+ZTH7Kl64lxhyO/9F84t47TctQhhFqxgsIxmv+ZVHajanNl4E0gXqJ0ULsY2h; SamlSession=aHR0cHMlM2ElMmYlMmZmcGNkcmRldi5tb2ZmaXR0Lm9yZyZGYWxzZSZDdWtyYXNTRCYmJiYmXzFkZjY4M2RhLTM4NTktNDVjNS04ODNkLTA3NmRiYTdiMjk3Yg==; MSISAuthenticated=NC8xNi8yMDE5IDExOjI2OjI4IEFN; MSISLoopDetectionCookie=MjAxOS0wNC0xNjoxMToyNjoyOFpcMQ==
Upgrade-Insecure-Requests: 1
HTTP/1.1 302 Found
Content-Length: 0
Content-Type: text/html; charset=utf-8
Location: https://example.com:443/login?SAMLRequest=lZLfa4MwEMf%2fFcl71KjxR7BCqS9C18I69rCXEjXpZJq4XCz982crY6yMwh7vuO9973N3OfChH9lWn%2fRkn8XnJMA6VblCR%2bpzQqWUOKWE4igMUlz7nGKexHUYJdSnKUXOqzDQabVCgesjpwKYRKXAcmXnlE8y7EeYxC%2bEsCBmYeamCX1DTjm7dIrbm%2fLd2hGY58mxaU0rzu6gpeysdbU5eb0%2bdQo5G61AXHtORjHNoQOm%2bCCA2YYd1k9bNtuzZilik4JRNJ3sRIucnbZ7tTdraYW5HykkPyNdhl4Bu23jsctotNWN7lGR33DNIn0s4gDCXHFRccWdac04Auh7XN5K8ObSc9cI8KyZwObeYlPku7ltVf7TbjN9GA6HMvcWeZEvFz8IuB6uUq24FEfSyjgNW47DlGY4og3F6RxjP4nbmid1kCV17v2h%2fE7%2beqDiCw%3d%3d&Signature=pT%2fSUpslARJlvOCah5VzZk4stZLIREyHmUFOO4siHUbkL5eJG4QsfYj9Pq%2bwxnOaPaevYkmiXq0rft3drTzJHspns9UbucyYQvEaSAZVmRTTyfPC3Z0EgVGSvtr0JL3nuDPsq2IfbToseuQQtJFsA%2b94D8KtaLjtUJxiMcQMHyg2yR00Ac3NGt9AsRg1X73X%2frt0XZDN9bSt4R8t%2bt2Yl2UsZsL4GHTGk7RbN3AUrYHsLtKeuN07umXqX3otVtHo%2f9tx2w2h1glYycYbFCk%2bWjox8Mej%2fiLLkpAhw9EXlhiTGrEJ2%2bcYvnQxGokOsz2vXEOoc3%2fhle27LuTPFMN9yw%3d%3d&SigAlg=http%3a%2f%2fwww.w3.org%2f2001%2f04%2fxmldsig-more%23rsa-sha256
Server: Microsoft-HTTPAPI/2.0
P3P: ADFS doesn't have P3P policy, please contact your site's admin for more details
Set-Cookie: SamlSession=; expires=Mon, 15 Apr 2019 11:26:39 GMT; path=/adfs
SamlLogout=aHR0cCUzYSUyZiUyZnJwcHNzb2Rldi5tb2ZmaXR0Lm9yZyUyZmFkZnMlMmZzZXJ2aWNlcyUyZnRydXN0Pz8/aHR0cHMlM2ElMmYlMmZmcGNkcmRldi5tb2ZmaXR0Lm9yZyZGYWxzZSZDdWtyYXNTRCYmJiYmXzFkZjY4M2RhLTM4NTktNDVjNS04ODNkLTA3NmRiYTdiMjk3Yj9fNTBhMTVmZmYtODUxNS00MzI4LWIwYTUtYTc2YjM0NzUwNTg1P3VybiUzYW9hc2lzJTNhbmFtZXMlM2F0YyUzYVNBTUwlM2EyLjAlM2FzdGF0dXMlM2FTdWNjZXNz; path=/adfs; HttpOnly; Secure
MSISAuthenticated=; expires=Mon, 15 Apr 2019 11:26:39 GMT; path=/adfs
MSISAuth=; expires=Mon, 15 Apr 2019 11:26:39 GMT; path=/adfs
ReturnUrl=aHR0cHM6Ly9ycHBzc29kZXYubW9mZml0dC5vcmc6NDQzL2FkZnMvbHMvP3dhPXdzaWdub3V0MS4w; path=/adfs; HttpOnly; Secure
MSISSignoutProtocol=U2FtbA==; expires=Tue, 16 Apr 2019 11:36:39 GMT; path=/adfs; HttpOnly; Secure
Date: Tue, 16 Apr 2019 11:26:39 GMT
SAML:
<samlp:LogoutRequest ID="_50a15fff-8515-4328-b0a5-a76b34750585"
Version="2.0"
IssueInstant="2019-04-16T11:26:39.875Z"
Destination="https://example.com/login"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
NotOnOrAfter="2019-04-16T11:31:39.875Z"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
> <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://myadfs.org/adfs/services/trust</Issuer> <NameID xmlns="urn:oasis:names:tc:SAML:2.0:assertion">USERNAME</NameID> <samlp:SessionIndex>_1df683da-3859-45c5-883d-076dba7b297b</samlp:SessionIndex> </samlp:LogoutRequest>
On subsequent, unsuccessful logouts:
HTTP:
GET https://myadfs.org/adfs/ls/?wa=wsignout1.0 HTTP/1.1
Host: myadfs.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://example.com/dashboard/data
Connection: keep-alive
Cookie: MSISLoopDetectionCookie=MjAxOS0wNC0xNjoxMToyODoyNlpcMQ==; SamlLogout=aHR0cCUzYSUyZiUyZnJwcHNzb2Rldi5tb2ZmaXR0Lm9yZyUyZmFkZnMlMmZzZXJ2aWNlcyUyZnRydXN0Pz8/aHR0cHMlM2ElMmYlMmZmcGNkcmRldi5tb2ZmaXR0Lm9yZyZGYWxzZSZDdWtyYXNTRCYmJiYmXzFkZjY4M2RhLTM4NTktNDVjNS04ODNkLTA3NmRiYTdiMjk3Yj9fNTBhMTVmZmYtODUxNS00MzI4LWIwYTUtYTc2YjM0NzUwNTg1P3VybiUzYW9hc2lzJTNhbmFtZXMlM2F0YyUzYVNBTUwlM2EyLjAlM2FzdGF0dXMlM2FTdWNjZXNz; ReturnUrl=aHR0cHM6Ly9ycHBzc29kZXYubW9mZml0dC5vcmc6NDQzL2FkZnMvbHMvP3dhPXdzaWdub3V0MS4w; MSISSignoutProtocol=U2FtbA==; MSISAuth=AAEAAFOnxdlEvO8Le/Gti39Bx6BFj1cEJ39/A6ogocbLbXlBnq07uT1v+MuAzZs0NqyB1Wmqx3O8oTwPancFPCEFrQbngzsvsWI/oAXmuDih8uBG9MVPfstAu/cFPXL95V2IIUjX6r3Tv08FqipxW/1CHa7QM8XvXU5a516zFsZTaxke+ITD3B+nGPsuQY+oVG47NhtoMHmCrbShjOBd9Wn6Q5FzDqbHlxD/5czDUXixYf8gg+MTNq9W+oT5J7TF6NaBb7o1QojY7c8UoJ4fQONwlMNE17TgGVomqN4N9qVPTShGSaTlM8C+er9SOWQiALfZHvH2sv8N0AIn9qpivuCzw9WlBQsO/yJowvaHxPM8wgwLBknSt39RswaSdGjrI18CcgABAAAAz9AfrV1onudL+YY+0zL4vWeCboTECwksETafeI44/o0n0DEBx8kVGELmmPqSKD216OFB+p4k0K//HTW+YnRiuFpk1dAnN+dmwirgwzohFU1A3lWq0pQcHFyui1xs1UHnzDZokvK+7r859oZP0XZ4pGGTZsjWyc2B32FgwfvpiKYKDsWALpajW9FRDnt1VnGyDSzsN3V6vQHmKIEBZn5wb3+b3DtB9hV/ZssxiE7Xf8V8l+144wE71YH4ETNbcX0VXKNlkL9x5R+EThMlzyNl2tAcGWSk+3xM3lhfTm3+8y5GEP3rtJjLQGZSPKUljPcZM/MU3EX3YRrCkYsAyhgpgAMAAKGsYkEEca74go1dVexUCjdky1zUJMng5a/ZmKCRWTYsPT2DCjR579a0Hr69s8nl36p8EgyqnyXPm/uiFp+LPp1CuCCuXe/QYFoySixCOEcJsnRbikBEAP/Bpj5UUifnqgyO7MHH1GQiXeOlw2llsPu7rdNiEqB4X6Hqhnn6xaasl+5iqvNkZSTi8DSQc/24MRT4VsAcJcO7eqxjQBluWr2cyvdr9pn4GigQ05WaXWfogo3BwPJzLUo+NngvLHfxyn1wDmUYghc+oxS+vJwTadiiSDDzrcTVTuVxw2xj6OVi8DXbyRii5+VTKolRK0qCa/4C4BCzOOGUkooktX/GecV6eNuk8xOdLsiybY9Ah5Z2WVgraDntw/w/pP/ij4v0jDLvDQjU+BIfGOpeV1jcG9VDObir5GYGfOm59DtlRpoy/kpjiDLWI8EE75DEFlhomeae0v4xBQ6XqgVd5lEcA2DTm/3Ophg31FA2M5J65yE4t7W7inIC4XjMWFOu3GCMse7ERYyFbq59vf+iSs6eyev7wXidvAekALmq6Gk2Ths2JR1TbV27E2+kgGhmvlgiShx67E9s2wrBfPKvV7+IMS9Xe1YPKpZAlfCwnkbQNonqAMQH5LsHq1K7DWrNTcon10TiOtlMbzin8FtNphcnChHYmBbDxpqrf5xwwYXbyznQnMfeDnjN7aPo909gwhfUGNltLTOZ81m6k9c3Z0C8ugvL61bbw3Ku42OZiOnoVcEYjf50bMWZQl/hUMlRp+uHVNhK41z6U2O9Ph7S4ZI4wg7z33Z+VCp+08HpMRqrX155atJYVX73mnr3+J4rKvyJvjglb9aA333MUOC7iGMDDNImibvofyhbqK3VO+zqyPYj0R4OvhnA9RlvV10MWDhn5qnVevA5Oo1MQNPGnTLtfRZXpB8oa2bZZMh62XO4a5gZ/ioNsigiDAFKbQnx0wvBTb0uqYSZpfxoA4K2o87swOYB81FTkQNBnNZG171szH89jijOuEAI7hAWdAnM2LjagGZwWpuF2yHbJqQqsGzjvnqbQ6yMTvaEbkooSelFEBeRW2Gg5rGAjj5Pvs+T0ljhVlby6FfFKJ71NDBvn/7PGIglARSZqUZcAuthlhr8pta11WnhsfnyumvLfWvOZHZZjWslKMLBpGEBe1WgcYBUBYUrUeHmCqDRy5Zc4KJXwGrY; SamlSession=aHR0cHMlM2ElMmYlMmZmcGNkcmRldi5tb2ZmaXR0Lm9yZyZGYWxzZSZDdWtyYXNTRCYmJiYmX2NlNDAwODQxLTA2ZDItNDI3Ni05MTRlLWU5N2ExYWRlZmQzZQ==; MSISAuthenticated=NC8xNi8yMDE5IDExOjI4OjI2IEFN
Upgrade-Insecure-Requests: 1
HTTP/1.1 200 OK
Cache-Control: no-cache,no-store
Pragma: no-cache
Content-Length: 8957
Content-Type: text/html; charset=utf-8
Expires: -1
Server: Microsoft-HTTPAPI/2.0
Date: Tue, 16 Apr 2019 11:28:45 GMT
SAML:
NO SAML SENT
You will see that on a successful logout ADFS sets the cookies to clear them while an unsuccessful logout does not. Also, the unsuccessful logout does not send the SAML logout request.
Lastly, when I clear the cookies in the browser, the first login/logout session will work as intended again, and all subsequent logouts will not. I can see the cookies are retained on subsequent logouts as ADFS is not getting the SAML logout request. I just don't understand how this works on the first logout but not the following logouts. I have looked in and out of passport-saml's code but can't seem to find the issue.
Any assistance would be great.
Here is my passport.js setup:
const fs = require('fs');
const passport = require('passport');
const SamlStrategy = require('passport-saml').Strategy;
require('dotenv').config();
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
passport.use(new SamlStrategy({
entryPoint: 'https://myadfs.org/adfs/ls',
issuer: 'https://example.com',
callbackUrl: process.env.NODESERVERURL + ':' + process.env.PORT + '/authenticate/adfs/postResponse',
privateCert: fs.readFileSync(__dirname + '/private/keys/fpcdr.key', 'utf-8'),
logoutUrl: 'https://myadfs.org/adfs/ls/?wa=wsignout1.0',
signatureAlgorithm: 'sha256'
},
function(profile, done) {
const username = profile.nameID.toLowerCase();
const email = profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'].toLowerCase();
const sessionIndex = profile.sessionIndex;
return done(null, {
username,
email,
sessionIndex
});
})
);
module.exports = passport;
passport callbackUrl:
module.exports.adfsAuthenticate = function(req, res) {
const email = req.user.email;
const username = req.user.username;
if (process.env.UAT === 'true') {
res.status(302).redirect(LANDING_PAGE_REDIRECT_DEV);
} else {
res.status(302).redirect(LANDING_PAGE_REDIRECT_PROD);
}
};
adfs logout:
module.exports.logout = function(req, res) {
req.logout();
req.session.destroy(function (err) {
if (!err) {
res.status(200).clearCookie('connect.sid', {path: '/'}).json({status: "Success"});
} else { alert(err); }
});
};
I have observed that there is one cookie which gets stored in browser called "MSISSignoutProtocol", if this cookie exist, the logout does not work as expected on subsequent requests.
To get it working, you might need to implement proper SAML logout which most IDP support.
Here is my solution, shared here
I found out why SLO doesn't work. I found a solution to make it work properly, im my case with Microsoft Azure (SAML-ADFS), using a database session storage. I log in from my app. Then I log out from microsoft azure ( not from my app), and it logs out nicely !
I think important to mention that there is a huge difference between the IDP logout request with all others. Most of the time, passport routes handle user device's initiated requests or redirects, That means the device's cookies comes along theses requests, which allows passport-saml to successfully retreive device's user information.
When the IDP broadcasts a logout request to relevant ISP(s), it is not always a device request (either initiated nor redirected). It is a simple HTTP GET request that contains a SAMLRequest. This SAMLRequest contains what the IDP uses to identify a saml user.
A saml user is identified using nameID and nameIDFormat profile properties.
Therefore, when a user logs in through an IDP, the app must store both values, nameID and nameIDFormat. You can acheive that first in you saml strategy authentication handler :
export const samlStrategy = new SamlStrategy({
...samlStrategyOptionsHere,
logoutCallbackUrl: 'https://your.isp.com/saml/logout'
},
(profile: Profile | null | undefined, done: VerifiedCallback) => {
// do login process. On successful login, don't forget
// to pass along nameID and nameIDFormat profile values !
done(null, {
userId: user.id, // in my case, my app just need to store the user id
nameID: profile?.nameID,
nameIDFormat: profile?.nameIDFormat
})
});
Then store nameID and nameIDFormat in passport user's session ( within serialization process ) :
interface SessionUserData{
userId: number,
nameID?: string,
nameIDFormat?: string,
}
passport.serializeUser<SessionUserData>((user: any, done) => {
// do serialization stuff
done(null, {
userId: user.id,
nameID: user?.nameID,
nameIDFormat: user?.nameIDFormat
})
});
SLO requests can then be handled properly by first retreiving passport sessions that holds the specified saml identification, and destroy them manually, like this :
interface SAMLLogoutExpectedResponse{
profile?: Profile | null | undefined;
loggedOut?: boolean | undefined;
}
authRouter.get('/saml/logout', async (req, res)=>{
// req.user may not exists here, since this is a single HTTP-GET request that contains a SAMLRequest
// initiated by the IDP, not a user device redirection
// use passport-saml to validate this request
const response:SAMLLogoutExpectedResponse = await (samlStrategy._saml as SAML).validateRedirectAsync(req.query, url.parse(req.url).query)
// check the « SAML USER » profile
if( response?.profile &&
response.profile?.nameID &&
response.profile?.nameIDFormat &&
response.loggedOut===true ){
// get related session(s) if exists
const sessionsToDestroy:string[] = ((await knex.from('sessions').where(true).select('*')) ?? []).reduce((acc:any[], session:any) => {
const userData:any = JSON.parse(session?.data ?? {})?.passport?.user
if( userData &&
userData.nameID &&
userData.nameIDFormat &&
userData.nameID == response?.profile?.nameID &&
userData.nameIDFormat == response?.profile?.nameIDFormat
){
acc.push(session.session_id)
}
return acc;
}, [])
if(sessionsToDestroy.length){
// destroy database session --> which invalidate any corresponding cookie on any device (if still exists)
await knex.from('sessions').whereIn('session_id', sessionsToDestroy).delete();
}
}
// if this is a IDP request, all ends here
// if user initiated the logout request, IDP will redirect device here. Send the device somewhere coherent after successful logout
res.redirect('/')
});
Voilà. I know this is a precise case, and doesn't fit to everyone. But the most important point is that a SLO callback must handle nameID and nameIDFormat values. It should not handle a device session.

MSGraph Authenticating to dynamics with correct token gives 401

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.

coinbase oauth2 sometimes failed to renew access token (using refresh token)

I am integrating coinbase in an iOS app and I am using Oauth2 to authenticate. I am able to get the access token after going through the usual workflow. I have also taken care to attempt refresh my access token whenever any requests hit a 401 (upon expiration) by calling this:
POST https://coinbase.com/oauth/token
Data:
grant_type=refresh_token&refresh_token=abcd1234&client_id=theclientid&client_secret=somesecretid
It works for a while but then from time to time, it would fail with a request response:
NSHTTPURLResponse: 0x15eb2730
{ URL: https://coinbase.com/oauth/token } { status code: 401, headers {
"CF-RAY" = "f67d477aae4052e-YYZ";
"Cache-Control" = "no-store";
Connection = "keep-alive";
"Content-Type" = "application/json; charset=utf-8";
Date = "Sun, 02 Feb 2014 15:14:14 GMT";
Pragma = "no-cache";
Server = "cloudflare-nginx";
"Set-Cookie" = "__cfduid=<some long alpha-numeric string>; expires=Mon, 23-Dec-2019 23:50:00 GMT; path=/; domain=.coinbase.com; HttpOnly";
Status = "401 Unauthorized";
"Strict-Transport-Security" = "max-age=31536000";
"Transfer-Encoding" = Identity;
Vary = "Accept-Encoding";
"Www-Authenticate" = "Bearer realm=\"Doorkeeper\", error=\"invalid_request\", error_description=\"The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.\"";
"X-Content-Type-Options" = nosniff;
"X-Frame-Options" = SAMEORIGIN;
"X-Rack-Cache" = "invalidate, pass";
"X-Request-Id" = "<some long alpha-numeric string>";
"X-Runtime" = "0.012066";
"X-Ua-Compatible" = "IE=Edge,chrome=1";
} }
Has anyone encounter this error before? I have assume the request URL is correct always. I am not sure why it would complained about "missing required parameter" or "unsupported parameter". I havent figured out a pattern of failure yet. Hopefully, someone out there may have seen this before.
I haven't been able to find any documentation directly about refresh_token requests, but I think you're supposed to also include the redirect_uri in your refresh_token request (based on this: https://coinbase.com/docs/api/authentication#collapse2).
Also, I noticed that my official coinbase app required a re-auth a few days ago, but when I log in to my coinbase account, it says that the app was authorized 25 days ago. So, perhaps even the request_tokens have a timeout? Did your request_token request fail after not using the app for a while?
Or maybe coinbase reset something and invalidated all of their access_tokens and refresh_tokens because my app, which was previously working fine as far as refresh_tokens, is now failing on the refresh_token request.
So, I would suggest having your app re-authorize when this happens, and get a new access_token and refresh_token, since I think that's what the official coinbase app does.

Getting Dropbox - 403 error when logging on using dropboxuploader.php

Hey I'm using dropboxuploader.php to login into dropbox. All was working fine, but when i came into work yesterday i could no longer connect. This is what dropbox is returning to me.
HTTP/1.1 100 Continue
HTTP/1.1 403 Forbidden
Server: nginx/1.2.3
Date: Thu, 04 Oct 2012 08:44:36 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
It seems you tried to do something we can't verify. Did you log into a different Dropbox account in a different window? Try clicking here to go back to the page you came from, or just go home.
Replace the login function with below code and it should work:
protected function login() {
$data = $this->request('https://www.dropbox.com/login');
$str = '<input type="hidden" name="t" value="';
$start = strpos($data,$str);
$val = "";
if($start !== false)
{
$val = substr($data,$start+strlen($str),24);
}
$data = $this->request('https://www.dropbox.com/login', true, array('login_email'=>$this->email, 'login_password'=>$this->password, 't'=>$val));
if (stripos($data, 'location: /home') === false)
throw new Exception('Login unsuccessful.');
$this->loggedIn = true;
}
Just update your dropbox uploader file instead doing your fixes.
https://github.com/jakajancar/DropboxUploader