I am having an issue when using ITfoxtec for ASP.NET Core 3.0.
As context I am trying to establish a connection between a webapplication and a third-party login service. To encapsulate some of the possibilities beforehand, the third-party has access to our metadata-url and configured their services for our webapplication.
Desired user workflow:
User enters the webapplication;
User clicks a button which redirects the user to the login service;
User logs in on the service and redirects back to the given returnURL;
Afterwards the webapplication determines permission based on the provided sso-cookie.
Steps taken so far:
Added Saml2 section in appsettings.json containing our metadata.xml and issuer. The issuer name equals the given EntityID provided within the metadata.xml. It is made anonymous in the given context, like so:
"Saml2": {
"IdPMetadata": "wwwroot/SAML/Metadata.xml",
"Issuer": "myIssuerName",
"SignatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
"CertificateValidationMode": "ChainTrust",
"RevocationMode": "NoCheck",
"SigningCertificateFile": "\\licenses\\certificate.pfx",
"SigningCertificatePassword": "password1"
},
Added Saml2Configuration in startup.cs;
services
.Configure<Saml2Configuration>(Configuration.GetSection("Saml2"))
.Configure<Saml2Configuration>(configuration =>
{
configuration.SigningCertificate = CertificateUtil.Load(
$"{Environment.WebRootPath}{Configuration["Saml2:SigningCertificateFile"]}",
Configuration["Saml2:SigningCertificatePassword"]);
configuration.AllowedAudienceUris.Add(configuration.Issuer);
var entityDescriptor = new EntityDescriptor();
entityDescriptor.ReadIdPSsoDescriptorFromFile(Configuration["Saml2:IdpMetadata"]);
if (entityDescriptor.IdPSsoDescriptor == null) throw new Exception("Failed to read the metadata.");
configuration.SignAuthnRequest = true;
configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices
.Where(ed => ed.Binding.ToString() == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")
.First().Location;
configuration.SignatureValidationCertificates.AddRange(entityDescriptor.IdPSsoDescriptor.SigningCertificates);
});
Here comes the tricky part; By default the sso initiation does a request with a RedirectBinding which does therefore send a GET request towards the sso service. However, the service I am trying to approach expects a SAMLRequest as a POST request. So I have changed the code by initiating with PostBinding request and afterwards directly submit the form, like so:
public IActionResult Initiate([FromQuery(Name = "returnUrl")] string returnUrl = "")
{
var binding = new Saml2PostBinding();
binding.SetRelayStateQuery(new Dictionary<string, string> { { "ReturnUrl", returnUrl } });
binding.Bind(new Saml2AuthnRequest(_saml2configuration)
{
ForceAuthn = false,
IsPassive = false,
NameIdPolicy = new NameIdPolicy() { AllowCreate = true },
AssertionConsumerServiceUrl = new Uri("https://localhost:44366/api/Authentication/Process"),
});
return binding.ToActionResult();
}
Issue:
However, after sending the base64 encoded AuthnRequest as SAML Request, I am receiving a 403 Forbidden from the third-party login. At this stage I am not certain whether is the identity provider not being configured properly or my request lacking something. What am I doing wrong?
Below is the (anonymously made) request headers.
Assume that the SAMLRequest is provided in formdata as base64 encoded.
:authority: myEntityDescriptorName
:method: POST
:path: mySsoURL
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
accept-encoding: gzip, deflate, br
accept-language: nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7
cache-control: no-cache
content-length: 3582
content-type: application/x-www-form-urlencoded
cookie: JSESSIONID=3D5FE88D55674C2F1E3646E6D8A0FFBE
origin: https://localhost:44366
pragma: no-cache
referer: https://localhost:44366/
sec-fetch-mode: navigate
sec-fetch-site: cross-site
sec-fetch-user: ?1
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36
It is correct to change the Authn Request to a Post binding if that is required.
Your application is a Service Provider (also called Relying Party) which needs to be configured at the Identity Provider with a unique Issuer name.
I think the problem is that the Issuer name you have configured ("Issuer": "myIssuerName") is incorrect. The issuer name should be your Service Providers issuer name, not the Identity Provider Issuer name from the metadata.xml.
Related
I am trying to use python requests to receive my access token for the Amazon Advertising API. The procedure is outlined here: https://advertising.amazon.com/API/docs/v2/guides/authorization Here is what I tried
CLIENT_ID = MyClientID
CLIENT_SECRET = MySecret
RETURN_URL = 'https://myreturn.com/my.php'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.61 Safari/537.36',
}
with requests.Session() as s:
s.headers = headers
r = s.get('https://www.amazon.com/ap/oa?client_id={}&scope=cpc_advertising:campaign_management&error=access_denied&response_type=code&redirect_uri={}'.format(CLIENT_ID,RETURN_URL),headers=headers)
soup = BeautifulSoup(html)
data = {}
form = soup.find('form', {'name': 'signIn'})
for field in form.find_all('input'):
try:
data[field['name']] = field['value']
except:
pass
data[u'email'] = MY_EMAIL
data[u'password'] = MY_PASS
b = s.post('https://www.amazon.com/ap/oa?client_id={}&scope=cpc_advertising:campaign_management&response_type=code&redirect_uri={}',data=data,allow_redirects=True,headers=headers)
i get an error_description=User+not+authenticated&error=access_denied error, what am I doing wrong here?
You DON'T NEED Username and Password in your Python Script to authenticate!
What you need is CLIENT_ID, SCOPE and REDIRECT_URI and three requests:
Get authorization code:
GET https://www.amazon.com/ap/oa?client_id={{CLIENT_ID}}&scope={{SCOPE}}&response_type=code&redirect_uri={{REDIRECT_URI}}
This will open the 'Login with Amazon' Consent Page, where you (or your customer) log into your Amazon Seller Central account and grant access to the Console APP with API access rights.
Request tokens
POST https://api.amazon.com/auth/o2/token
with headers:
Content-Type:application/x-www-form-urlencoded
with body data:
grant_type:authorization_code
code:{{AUTH_CODE}} <----- returned from step 1
client_id:{{CLIENT_ID}}
client_secret:{{CLIENT_SECRET}}
redirect_uri:{{REDIRECT_URI}}
Get/Refresh access token (every time it is outdated):
POST https://api.amazon.com/auth/o2/token
with headers:
Content-Type:application/x-www-form-urlencoded
charset:UTF-8
with body data:
grant_type:refresh_token
refresh_token:{{REFRESH_TOKEN}} <------ returned from step 2
client_id:{{CLIENT_ID}}
client_secret:{{CLIENT_SECRET}}
With the CLIENT_ID and (fresh) access token you can now request every service from the API. For excample listCampaigns:
GET https://advertising-api.amazon.com/v2/sp/campaigns
Headers:
Content-Type:application/json
Amazon-Advertising-API-ClientId:{{CLIENT_ID}}
Amazon-Advertising-API-Scope:{{PROFILE_ID}}
Authorization:Bearer {{ACCESS_TOKEN}} <----- returned from step 3
I'm learning how to secure a test API that I'm building and I want to implement security where a user signs up and then he requests an API Key which he will use in his app to authenticate to my API.
I started to implement this: https://github.com/vchatterji/OAuth2ClientCredentialGrant and I got the first part working where a user can signup and then request and receive a ConsumerKey & ConsumerSecret which is saved in an Azure table.
My problem is that I'm not sure what Flow I'm using The documentation doesn't state to change anything in StartupAuth:
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
I'm trying to authenticate with Fiddler and have tried many different requests but my most common error is unsupported grant type.
Based on what I have here what kind of grant type should I use?
Above it says to authenticate at /Token but other docs say api/token, which is the correct one?
Any help with composing the auth request would be greatly appreciated.
I was having the same issue for not having the data sent in the body of the request... Like this:
POST http://localhost:60543/oauth/token HTTP/1.1
Host: localhost:60543
Connection: keep-alive
Content-Length: 54
Cache-Control: no-cache
Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36
Postman-Token: 85363928-a3d6-f9ad-c368-ab6019027a02
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en,fr;q=0.8,pt-PT;q=0.6,pt;q=0.4,en-US;q=0.2,es;q=0.2
username=peter&password=123456&grant_type=password
Make sure to set the content type in the header of the request.
I'm trying to set a cookie in my application.
Here's the code that sets the cookie:
public HttpResponseMessage LogIn(UserLoginVM user)
{
// Do login stuff
var cookie = new CookieHeaderValue("STUPID-COOKIE", "12345");
cookie.Domain = Request.RequestUri.Host;
cookie.Path = "/";
cookie.HttpOnly = true;
// Get user's profile
HttpResponseMessage res = Request.CreateResponse<UserProfileVM>(HttpStatusCode.OK, profile);
res.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return res;
}
The response from the server is the following:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Set-Cookie: STUPID-COOKIE=12345; domain=localhost; path=/; httponly
Access-Control-Allow-Origin: *
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUFJPSkVDVFNcU2Ftc2tpcC5TZXJ2aWNlV2ViTmV3XFNhbXNraXAuQXV0aEFQSVxTYW1za2lwLkF1dGhBUElcbG9naW4=?=
X-Powered-By: ASP.NET
Date: Wed, 18 Feb 2015 11:58:07 GMT
Content-Length: 8019
Notice the following header:
Set-Cookie: STUPID-COOKIE=12345; domain=localhost; path=/; httponly
However, when I go under "Cookies" in "Resources" tab in Chrome, nothing is set. Also when I send a request to the server no cookie is in the headers.
Here's the code that reads the cookie:
CookieHeaderValue cookie = Request.Headers.GetCookies("STUPID-COOKIE").FirstOrDefault();
cookie variable is always null.
My application is running on http://localhost:53998 and the authentication service is running on http://localhost:60858
My Chrome version is 40.0.2214.111.
Here's a GIF demonstrating the problem:
http://i.imgur.com/q7lkXBz.gif
Edit: This seems to be non-specific to Chrome. This doesn't work on FireFox (v35) either. GIF: http://i.imgur.com/ZewnEtc.gif
I ran into this issue today and Gaui's answer was quite useful to me, bearing in mind ideally you do not want to open up your server to CORS requests from any site. I'm in my dev environment and my server and client are on different origins since they are on different ports on localhost. Also I'm using .net core 3.0
My issue was caused by my server not sending cookies to my client side as my CORS settings were blocking the sending of cookie to my domain this was evident by the server not using the header Access-Control-Allow-Credentials: true. To resolve this I configured my server in Startup.cs to allow requests from my client side to allow credentials (A credential is a cookie's authorization headers or TLS client certificates).
var allowedOrigins = new[] {"localhost:3000"}; // Ideally comes from appsettings
app.UseCors(builder =>
builder.WithOrigins(allowedOrigins).AllowCredentials().AllowAnyMethod().AllowAnyHeader().Build());
For the cookie options; I found that the you do not have to set Domain if you do not want to, Secure works even when the site is not using https.
Google chrome now supports cookies on localhost, I believe it didn't used to as a lot of older SO posts have users who faced that issue.
On the client side, you need to configure it to accept cookies as well, as in Gaui's answer above. I was using fetch, and so had to add the option:
credentials: 'include'
Which tells fetch to retrieve cookies across domains. See the docs here
I highly suspect that localhost is not a valid domain name so Chrome rejects it. If you simply remove 'domain=localhost' from the Set-Cookie then it will work and Chrome will assign the domain to localhost for you.
I would personally create a local domain name like "test.dev" and add it to your Windows hosts file, 127.0.0.1 test.dev
I finally managed to solve this.
In the response from the API I had to add Access-Control-Allow-Credentials: true headers, or by adding the following in the WebApiConfig class:
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
}
Then I had to enable it on the client-side, by setting the withCredentials property in the XMLHTTPRequest object to true. In AngularJS app config function you can do the following:
$httpProvider.defaults.withCredentials = true;
Also, for Chrome, I had to omit the Domain (or set it as null).
Hope this helps others struggling with this.
I try to do a POST request with HttpRequest (dart:html) to call a rest service secured with basic authentication.
HttpRequest request = new HttpRequest(); // create a new XHR
var url = "http://localhost:8082/xyz";
request.open("POST", url, async: false); // POST the data to the server
String username = "foo";
String password = "bar";
final auth = CryptoUtils.bytesToBase64(UTF8.encode("$username:$password"));
request.setRequestHeader('authorization',"Basic $auth");
request.setRequestHeader('content-type',"application/json");
request.setRequestHeader("accept", "application/json");
String jsonData = '{"language":"dart"}'; // etc...
request.send(jsonData); //exception 401 Unauthorized
Before performing the POST call the OPTIONS call is performed (issued by dart:html) without the authorization header. This leads into an 401 Unauthorized response.
Request header:
Accept:*/*
Accept-Encoding:
gzip,deflate,sdch
Accept-Language:
en-US,en;q=0.8
Access-Control-Request-Headers:
authorization, content-type, accept
Access-Control-Request-Method:
POST
Cache-Control:
max-age=0
Connection:
keep-alive
Host:
localhost:8082
Origin:
http://localhost:63342
Referer:
http://localhost:63342/dart_client/test/all_test.html
User-Agent:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.0 (Dart) Safari/537.36
Reponse header:
Content-Length:
0
Date:
Mon, 02 Feb 2015 23:33:58 GMT
Server:
Jetty(9.2.7.v20150116)
WWW-Authenticate:
basic realm="xyzrealm"
Is there a way to provide the authorization header to the OPTIONS call?
Any suggestions would be great. Thanks
The OPTIONS request is made by the browser automatically and you can't modify that request. The server needs to allow the OPTIONS preflight request without authentication.
See http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
The user credentials are excluded.
I have an action that generates a password reset link and emails it to the user
public ActionResult SendResetPasswordEmail(string userName)
{
var webUser = LoadUser(userName);
if (webUser != null)
{
var token = WebSecurity.GeneratePasswordResetToken(webUser.UserName);
emailSender.SendPasswordResetEmail(webUser, token, resetAction);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "No user found with username: " + userName);
}
The first time I call the action from the browser, I get an HTTP 200 response (and hit my breakpoint in the action).
The second time I call the action from the browser, I get an HTTP 304 response indicating that the content is unchanged.
There are no [OutputCache] attributes anywhere in the source file (not on the class or the action).
What is causing the web server to decide that the content is unchanged and return the HTTP 304?
I'm aware of a work-around
https://stackoverflow.com/a/18620970/141172
I'm interested in understanding the root cause for the HTTP 304 response.
Update
Headers on first request:
Request Headers
Request GET /Companies/SendResetPasswordEmail/?userName=ej HTTP/1.1
X-Requested-With XMLHttpRequest
Accept */*
Referer http://local:6797/Companies
Accept-Language en-US
Accept-Encoding gzip, deflate
User-Agent Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host localhost:6797
DNT 1
Connection Keep-Alive
Cookie __RequestVerificationToken=sNOBS6qz32LtnJpLWgHHELhaE44DfIVE1LSMUgjzHjcwsvxlUFa4lOSyA5QeB8keLXYL08Psjg29CRI7W73uHLJy6A81; .ASPXAUTH=DAF8AF47E955F723EE9438866BE1B4BFBF91BA01912EF087824F03581DBCA05A4AECA01373FAF40DF0C4D5C17F17DEFA2F85C1B702988B7E0F750BFE19566FC711C7D6BD81D8F0B0ABD68AF5B3D9BA032286361F; ASP.NET_SessionId=5e2gcvkc2p3rji25z5emyqzd; HelixPlugins1.0=IEPlugin1.0
Response Headers
Response HTTP/1.1 200 OK
Server ASP.NET Development Server/11.0.0.0
Date Thu, 03 Apr 2014 23:29:02 GMT
Cache-Control private, s-maxage=0
Content-Length 0
Connection Close
NOTE: I changed localhost to local in the above because StackOverflow does not allow links containing localhost to be posted :-)
The browser is Internet Explorer 10.
IE caches ajax responses by default, you need to explicitly tell it not to do any ajax caching by setting your ajax object's cache property to false.
Browsers such as Chrome automatically append a random token to your request to make it unique.