Can't read document.cookie client side - puppeteer-sharp

Javascript gives error: "DOMException: Failed to read the 'cookie' property from 'Document': Access is denied for this document."
Iv'e set all cookies to use use HttpOnly = false but it do not seem to help.
The page works perfectly if I call the page without puppeteer.
I currently copy all session variables from the request I am initializing puppeteer with:
var cookies = new List<CookieParam>();
foreach (var key in request.Cookies.Keys)
{
var cookie = request.Cookies.Get(key.ToString());
cookies.Add(new CookieParam {
Name = cookie.Name,
Value = cookie.Value,
HttpOnly = false,
Domain = cookie.Domain,
Url = baseUrl,
Path = cookie.Path,
Secure = cookie.Secure
});
}
await page.SetCookieAsync(cookies.ToArray());

I were using page.SetContent(...) to create the page content. This do not seem to allow usage of cookies client side.
I switched to use page.GoToAsync(...) which avoided the problem.

Related

Identity server 4 throws redirect uri not define error

I am trying connect the asp.net webform client to identity server 4 for authentication and athorization. When user is redirected to identity server for login I am getting an error and Identity server log says "redirect_uri is missing or too long" but I have defined the redirect uri on client config. Not sure why it is throwing an error on identity server side?
Client Configuration:
new Client {
ClientId = "testclient1",
ClientSecrets = { new Secret("client_secret_webform".ToSha256()) },
AllowedGrantTypes = GrantTypes.Implicit,
RequirePkce = true,
RedirectUris = { "http://localhost:54602/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:54602/signin-oidc" },
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
},
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
}
Webform Client setting
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "testclient1",
Authority = "https://localhost:44314/",
ClientSecret = "client_secret_webform",
ResponseType = "id_token token",
SaveTokens = true
});
}
IdentityServer4.Validation.AuthorizeRequestValidator: Error: redirect_uri is missing or too long
The easiest way to figure out the correct redirectUrl is to locate the request to the Authenticate endpoint in Fiddler, and see what RedirectUrl the OpenIDConnect handler is sending to IdentityServer.
In Fiddler you can then locate the redirect_uri that should match exactly the url defined in IdentityServer
Try to change OpenID Connect settings on client to add RedirectUri and PostLogoutRedirectUri, like this:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "testclient1",
Authority = "https://localhost:44314/",
ClientSecret = "client_secret_webform",
ResponseType = "id_token token",
SaveTokens = true,
RedirectUri = "http://localhost:54602/signin-oidc",
PostLogoutRedirectUri = "http://localhost:54602/signin-oidc",
});
}
Also as other answer mentioned use Fiddler or activate log to check the values sent to IDS4
The redirect uri you configure in IdentityServer is not the same as the one you are actually using in the client request. Capture your request to see this redirect uri.
The exception can be caused by two resons:
The redirect Uri is empty, null or white space
The redirect Uri is longer than the maximum allowed. The default value is 400.
You can modify this value in the IdentityServerOptions:
services
.AddIdentityServer(options =>
{
options.InputLengthRestrictions.RedirectUri = 1000;
...
}
...
...

How to configure CodeIgniter and Ion Auth with front and back ends into different domains

I'm working on a modernization of and old CodeIgniter Application. By separateing the application in front and back ends.
The new Front end is an angular SPA application. The backend still uses a of CI application. The CI uses Ion_Auth to authorize users.
Everything works fine if both are into the same domain, for instance localhost
The problem is that each part must be on different url domains, ie.:
Frontend - localhost:8081, in the future will be example.com
Backend - localhost:8082, in the future will be api.example.com
This way Ion Auth is able to log-in users, but when I query if user is logged in, it returns false. The CI doesent holds the session anymore.
I also noted that when both are into the same domain the session folder only contains only one file, which means that CI recognizes the session.
When I set it onto different domains, the session folder creates a new file for each XHR Request. That means that CI is not holding session anymore.
Why this is happening? What Should I do to make front and backend to work properly with Ion Auth?
Here are my CI configuration:
$config['sess_driver'] = 'files';
$config['sess_cookie_name'] = 'ci_session';
$config['sess_expiration'] = 7200;
$config['sess_save_path'] = BASEPATH . 'var/session/';
$config['sess_match_ip'] = FALSE;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = FALSE;
$config['cookie_prefix'] = '';
$config['cookie_domain'] = '';
$config['cookie_path'] = '/';
$config['cookie_secure'] = FALSE;
$config['cookie_httponly'] = FALSE;
$config['csrf_protection'] = FALSE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array();
I also set :
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
And this is how I'm making the XHR requests.
$http({
url: $rootScope.API_URL + "/user/check",
method: "POST",
headers: { "Content-Type": undefined },
data: { /*...*/ }
}).then( /*...*/ )
I Just found at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#section_5
The most interesting capability exposed by both XMLHttpRequest or Fetch and CORS is the ability to make "credentialed" requests that are aware of HTTP cookies and HTTP Authentication information. By default, in cross-site XMLHttpRequest" or Fetch invocations, browsers will not send credentials. A specific flag has to be set on the XMLHttpRequest" object or the Request constructor when it is invoked.
(Special Highlight to the NOT keyword in the sentence).
I other words. It will no work anyway. A special flag must be present in the XHR request. When using AngularJS $hhtp object I must use withCredentials: true
$http({
url: ...
withCredentials: true
})
Also need to set both headers in API responses:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost
Unfortunately I was not able to use* as Access-Control-Allow-Origin, instead I must use the correct domains.
'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
This way I was able to make Cross origin XHR Requests with cookies preserving sessions.

How to know that access token has expired?

How should client know that access token has expired, so that he makes a request with refresh token for another access token?
If answer is that server API will return 401, then how can API know that access token has expired?
I'm using IdentityServer4.
Your api should reject any call if the containing bearer token has already been expired. For a webapi app, IdentityServerAuthenticationOptions will do the work.
But your caller Web application is responsible for keeping your access_token alive. For example, if your web application is an ASP.Net core application, you may use AspNetCore.Authentication.Cookies to authenticate any request. In that case, you can find the information about the token expiring info through OnValidatePrincipal event.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
//ExpireTimeSpan = TimeSpan.FromSeconds(100),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async x =>
{
if (x.Properties?.Items[".Token.expires_at"] == null) return;
var now = DateTimeOffset.UtcNow;
var tokenExpireTime = DateTime.Parse(x.Properties.Items[".Token.expires_at"]).ToUniversalTime();
var timeElapsed = now.Subtract(x.Properties.IssuedUtc.Value);
var timeRemaining = tokenExpireTime.Subtract(now.DateTime);
if (timeElapsed > timeRemaining)
{
//Get the new token Refresh the token
}
}
}
}
I have added a full implementation about how to get a new access token using refresh token in another StackOverflow answer

Separating Auth and Resource Servers with AspNet.Security.OpenIdConnect - the Audience?

The example on the AspNet.Security.OpenIdConnect.Server looks to me like both an auth and resource server. I would like to separate those. I have done so.
At the auth server's Startup.Config, I have the following settings:
app.UseOpenIdConnectServer(options => {
options.AllowInsecureHttp = true;
options.ApplicationCanDisplayErrors = true;
options.AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.Issuer = new System.Uri("http://localhost:61854"); // This auth server
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = new PathString("/token");
options.UseCertificate(new X509Certificate2(env.ApplicationBasePath + "\\mycertificate.pfx","mycertificate"));
});
I have an AuthorizationProvider written, but I don't think it's relevant to my current issue (but possibly relevant). At its GrantResourceOwnerCredentials override, I hard-code a claims principal so that it validates for every token request:
public override Task GrantResourceOwnerCredentials(GrantResourceOwnerCredentialsNotification context)
{
var identity = new ClaimsIdentity(OpenIdConnectDefaults.AuthenticationScheme);
identity.AddClaim(ClaimTypes.Name, "me");
identity.AddClaim(ClaimTypes.Email, "me#gmail.com");
var claimsPrincipal = new ClaimsPrincipal(identity);
context.Validated(claimsPrincipal);
return Task.FromResult<object>(null);
}
At the resource server, I have the following in its Startup.config:
app.UseWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")), branch =>
{
branch.UseOAuthBearerAuthentication(options => {
options.Audience = "http://localhost:54408"; // This resource server, I believe.
options.Authority = "http://localhost:61854"; // The auth server
options.AutomaticAuthentication = true;
});
});
On Fiddler, I ask for a token, and I get one:
POST /token HTTP/1.1
Host: localhost:61854
Content-Type: application/x-www-form-urlencoded
username=admin&password=aaa000&grant_type=password
So now I use that access token to access a protected resource from the resource server:
GET /api/values HTTP/1.1
Host: localhost:54408
Content-Type: application/json;charset=utf-8
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI.....
I now get this error - Audience validation failed. Audiences: 'empty'. Did not match validationParameters.ValidAudience: 'http://localhost:54408' or validationParameters.ValidAudiences: 'null'.
I think the reason why is because I never set an audience at the auth server (at app.UseOpenIdConnectServer(...)), so I don't think it wrote audience info to the token. So I need to set an audience at the auth server (as what is done in IdentityServer3), but I can't find a property on the options object that would let me do that.
Does AspNet.Security.OpenIdConnect.Server require the auth and resource to be in the same server?
Is setting the audience done when putting together the ClaimsPrincipal, and if so, how?
Would I need to write a custom Audience validator and hook it up to the system? (I sure hope the answer to this is no.)
Does AspNet.Security.OpenIdConnect.Server require the auth and resource to be in the same server?
No, you can of course separate the two roles.
As you've already figured out, if you don't explicitly specify it, the authorization server has no way to determine the destination/audience of an access token, which is issued without the aud claim required by default by the OAuth2 bearer middleware.
Solving this issue is easy: just call ticket.SetResources(resources) when creating the authentication ticket and the authorization server will know exactly which value(s) (i.e resource servers/API) it should add in the aud claim(s).
app.UseOpenIdConnectServer(options =>
{
// Force the OpenID Connect server middleware to use JWT tokens
// instead of the default opaque/encrypted token format used by default.
options.AccessTokenHandler = new JwtSecurityTokenHandler();
});
public override Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
var identity = new ClaimsIdentity(context.Options.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "unique identifier");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
context.Options.AuthenticationScheme);
// Call SetResources with the list of resource servers
// the access token should be issued for.
ticket.SetResources("resource_server_1");
// Call SetScopes with the list of scopes you want to grant.
ticket.SetScopes("profile", "offline_access");
context.Validate(ticket);
}
return Task.FromResult(0);
}
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "resource_server_1",
Authority = "http://localhost:61854"
});

keep session in node.js with proxy

I wish to connect to site (E) and within an html page access to my odata-server using odata protocol.
I've create a proxy in site (E):
this.use('/data', function(req, res) {
var request = require('request');
var apiUrl = process.env.ODATA_SERVER || 'http://localhost:5000';
url = apiUrl + req.url;
response = req.pipe(request(url))
response.pipe(res);
});
this is the scenario: A user connect to site (E), login and a dynamic page should load data from odata-server.
But the problem is: as the browser receive the data I lose the authenticate session with the user and E. How can I keep user login?
I've added this in my authenticate function:
(request({uri: uri, jar: true })).auth(loginName, password, false);
and now it works! :)
UPDATE: see my last comment :)

Resources