I am trying to download files from a website with username/password. You need to pay for a registered account in order to download files - which we have done. I am attempting to pass in the username/password and download a file as follows:
if (docUrl != null)
{
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
this.WebClientInstance.Credentials = new NetworkCredential(username, password);
fileData = this.WebClientInstance.DownloadData(docUrl);
this.WebClientInstance.Dispose();
isDataDownloaded = true;
}
WebClientInstance is a System.Net.WebClient. I debugged and verified that it is hitting the line to set credentials. Instead of downloading the PDF, I end up with an HTML page that prompts me to log in to get access to the file. I have verified that the username/password is correct. I use the same credentials to scrape the website with WatiN.
Is there something else that I'm supposed to be doing here?
UPDATE
Okay, I've done some sniffing around and found some useful info on this issue. I still haven't gotten it to work, but I think I'm closer. First, you need to create a cookie aware WebClient that extends the WebClient class, as follows:
public class CookiesAwareWebClient : WebClient
{
public CookieContainer CookieContainer { get; private set; }
public CookiesAwareWebClient()
{
this.CookieContainer = new CookieContainer();
}
protected override WebRequest GetWebRequest(Uri address)
{
var webRequest = base.GetWebRequest(address);
if (webRequest is HttpWebRequest)
(webRequest as HttpWebRequest).CookieContainer = this.CookieContainer;
return webRequest;
}
}
Next is to use the WebClient.UploadValues() method to upload the login info to the target website. The full process of authenticating and downloading the target resource is as follows:
using (var webClient = new CookiesAwareWebClient())
{
var postData = new NameValueCollection()
{
{ "userId", username },
{ "password", password }
};
webClient.UploadValues(docUrl, postData);
fileData = webClient.DownloadData(docUrl);
}
I was wrong about the site using forms auth. It is a JSP website and uses a JSESSIONID. I have verified that I am getting a cookie back with what appears to be a valid 32-byte JSESSIONID value.
However, when I call WebClient.DownloadData() it is still only returning the redirected login page. I've tried to fix this by setting the AllowAutoRedirect property on the HttpWebRequest to false, but then it returns 0 bytes.
Is there something else that I need to do so it won't redirect and will take me to the resource once I have authenticated?
(Answered in a question edit. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
Solved. So the problem was between my ears. I was passing in the URL for the secure resource to the .UploadValues() method, knowing that it would redirect to the login page. However, I really needed to pass in the URL from the login form (where it goes upon submitting) - not the login page itself. Once I did that, it worked correctly. I think I'm going to go find a career in food service now.
LINKS
There were already a few questions posted on SO that addressed this issue. I just didn't know what I was looking for at first so I didn't see those... Anywhere here are a couple good resources that I came across when working on this issue:
how to maintaine cookies in between two Url's in asp.net
Trying to get authentication cookie(s) using HttpWebRequest
Related
I followed this official sample to integrate Azure AD authentication into my ASP.NET Core application. Everything is working well, and I can call the MS Graph API successfully.
The problem is that after I signed in, I kept the browser opening and then I shutdown my application (simulate the server crashed), then I run the application again, then refreshing the page, I got an exception as shown in this screenshot:
I debugged the code, and I found that the request went into my controller (I had [Authorize] on my controller so it should redirect to sign in page when app found the request doesn't be authenticated) and the exception appeared when run
var me = await _graphClient.Me.Request().GetAsync();
This means the app thinks the request is authenticated.
[Authorize]
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphClient;
public HomeController(GraphServiceClient graphClient, ILogger<HomeController> logger)
{
_logger = logger;
_graphClient = graphClient;
}
public async Task<IActionResult> IndexAsync()
{
var me = await _graphClient.Me.Request().GetAsync();
ViewBag.Myname = me.DisplayName;
return View();
}
}
The reason for why the app not redirected to sign in page is that the browser stored cookie, after removing the cookie manually, the exception wouldn't appear and app would redirect to sign in page.
I think I need a global filter to handle the exception, by removing the cookie (I'm not sure if it can remove the cookie) and then redirect to sign in page manually, but I failed to find any document to describe how to redirect to sign in page manually by code.
Any document or code sample is appreciated.
By the way, should we put effort on this kind of scenario?
• As per my understanding on your query regarding the redirection to sign in page manually through code, I would suggest you to please go through the below code which suggests the same mechanism as you are expecting, i.e., delete the cookie from browser memory for the session signed in and redirect to the sign in page again for login to a new session, but the below code is for ‘laravel’: -
public function logout(Request $request)
{
$cookie = \Cookie::forget('first_time');
$this->guard()->logout();
$request->session()->invalidate();
return $this->loggedOut($request) ?: redirect('/')-
>withCookie($cookie);
Also, you can use ‘\Cookie::queue(\Cookie::forget('first_time'));’ to avoid creating the cookie variable and redirecting with it.
Kindly find the link below for more details: -
https://laracasts.com/discuss/channels/laravel/cookie-is-not-deleting-using-cookieforget-after-logout
• Similarly, I would suggest you follow the cookie-based identity authentication method using the ASP .Net Core sample code as below: -
using Microsoft.AspNetCore.Authentication.Cookies;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();
builder.Services.AddAuthentication
(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
options.SlidingExpiration = true;
options.AccessDeniedPath = "/Forbidden/";
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapDefaultControllerRoute();
app.Run();
For more information, kindly find the documentation link below which explains the cookie authentication configuration in detail: -
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-6.0
If I specify a redirect URI in my OpenIdConnectAuthenticationOptions like so
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
RedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
Then I get an infinite re-direct loop. This only happens though when i put it on and standalone IIS Server (our test server). If i remove all the Replay url's in AAD and leave it only setup for the test server, and remove the "RedirectUri = redirectUri," from the above my problem goes away.
I have a fiddler log here : https://drive.google.com/file/d/0B5Ap95E_wdyAa0RLLWloZ0dCaGM/view?usp=sharing
It appears that when request from AAD comes back to my app, before the token is grabbed and used, the Middle Ware is just bouncing it right back with a 302. Also what may be important, I have the [Authorize] attribute over the mvc controller that the routing and return uri directs to. If I remove it i do not get this issue.
[UPDATE]
I tried moving the application to my localhost install of IIS rather than using iisexpress so that i could setup as a SubApplication like it is on my iis server. On my localhost it does the same infinite loop. I added in some telemetry custom events on an override of the [Authorize] attribute and have been able to discover that when page is re-directed back to the application after authentication httpContext.user.identity.IsAuthenticated = false. So somehow the OWIN middle ware is not setting this to true?
Thanks for any help!
I was able to find a solution to my problem. Originally i was specifying my reply url's to point to the root of the site. My rout config looks like this:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Welcome", action = "Index", id = UrlParameter.Optional }
);
If I append "Welcome" to the end of my reply url it works. For some reason if i leave the reply url to the root of the site and have the default route picked up it just goes into an infinite loop.
I found out also that this only applies to a sub application of a site. I tried moving my application to be a standalone site in iis so rather than and I didnt have to add the controller name in the reply url.
Example:
Original reply url:
mysite.mydomain.com/CustomApp
New Reply url:
mysite.mydomain.com/CustomApp/Welcome
Hope someone else can find this useful!
UPDATE
I found out that the root of the problem was still caused from this mvc5 bug: katanaproject.codeplex.com/workitem/197. I thought it had been fixed but it has not, so i will continue to use the well known Kentor Owin Cookie Saver: github.com/Sustainsys/owin-cookie-saver
Solved by using Never option for CookieSecureOption
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieSecure = CookieSecureOption.Never
})
I have a situation where I'm using Credentials auth successfully, but I sometimes need to be able to simply create an authenticated session from inside a service using nothing but the user's email address. How can I do that? I suppose I'm looking for something similar in intent to FormsAuthentication.SetAuthCookie(), but I haven't found it.
Here's my thinking so far. Assuming I have to construct this myself, I see this inside CredentialsAuthProvider.TryAuthenticate:
if (authRepo.TryAuthenticate(userName, password, out userAuth))
{
session.PopulateWith(userAuth);
session.IsAuthenticated = true;
session.UserAuthId = userAuth.Id.ToString(CultureInfo.InvariantCulture);
session.ProviderOAuthAccess = authRepo.GetUserOAuthProviders(session.UserAuthId)
.ConvertAll(x => (IOAuthTokens)x);
return true;
}
That seems to imply that I can do what's inside the block myself via a UserAuth object from IUserAuthRepository.GetUserAuthByUserName(). I don't think this is enough though, since the session isn't saved here. Working backwards, I found that TryAuthenticate is called by Authenticate which then goes on to call OnAuthenticated where other things happen including saving the session. Can I resolve an instance of CredentialsAuthProvider and call OnAuthenticated myself?
Am I on the right track, or is this the completely wrong way of going about this?
sometimes need to be able to simply create an authenticated session from inside a service using nothing but the user's email address. How can I do that? I suppose I'm looking for something similar in intent to FormsAuthentication.SetAuthCookie()
I think the simplest, most FormsAuthentication.SetAuthCookie() way I can think of is to modify the AuthUserSession within in your service. The code below will get the Session, set IsAuthenticated to true, set the email and save the Session.
public class SomeService : Service
{
public String Any(SomeRequest request)
{
//not sure you need it, but you could put some code here to verify the email is allowed to authenticate
//and if true run the code below
var sess = this.SessionAs<AuthUserSession>();
sess.IsAuthenticated = true;
sess.Email = "test#email.com";
this.SaveSession(sess);
return "success";
}
}
Does anyone have any ideas as to why CredentialCache.DefaultCredential would return an ICredential instance with empty strings for domain, username, and password? I'm running a WCF service on IIS 7.5. It works fine on one server but never works on another. I have verified that the IIS application has Windows Authentication enabled....
Here is how it's being used:
string url = string.Format("{0}/departments/finance/_vti_bin/listdata.svc", _IntranetAddress);
var financeDataContext = new FinanceDataContext(new Uri(url))
{
Credentials = CredentialCache.DefaultCredentials
};
I am not sure how it is working in one of your servers? I hope you already read this
http://msdn.microsoft.com/en-us/library/system.net.credentialcache.defaultcredentials.aspx
but it clearly says "The ICredentials instance returned by DefaultCredentials cannot be used to view the user name, password, or domain of the current security context."
The NetworkCredential returned from CredentialCache.DefaultCredential is just a placeholder. If you look at it using the Debugger, you'll see that it's of type SystemNetworkCredential. Internal API check for this type to see if integrated authentication should be used or not. There are other ways to get the current username (like WindowsIdentity.GetCurrent()).
EDIT:
To specify impersonation for a WCF operation, add this attribute to the method implementing a contract:
[OperationBehavior(Impersonation = ImpersonationOption.Required)]
public void SomeMethod()
{
// do something here
}
I am running a CMS web site on WSS 3.0.
I would like to have a custom sign-in page for the publishers. Do I have any other alternative other than the Welcome control? (For example, could I use ASP.NET Login control?
Thank you for your help.
That would depend on the authentication mechanism that you use. If you're using Active Directory, you're pretty much tied to the Welcome control.
If however you're using Forms Based Authentication, you can control to login page more completely.
FBA can be tricky to configure and I'd recommend staying with AD if you can, but if you have to go FBA, here's a good guide:
http://technet.microsoft.com/en-us/library/cc262201(office.12).aspx
This is really not much difficult.
It can only be happen if you have Forms based authenticated site not windows based, then you must have to modify login.aspx page.
this relies in _layouts folder of 12 hive. so you have to modify it.
Best way to do is, fo to _layouts folder, make a copy of it and paste it in somewhere in the disk and then change the location in IIS properties for the site of the _layouts folder to your copied one. and make the changes of that login page.
Points to remember.: It uses a master page and there are 5 or 6 customplaceholders requires. so do have them in your new masterpage.
Next is about the code behing for login control to work.
If you are customizing your login code. then you have to modify
this is an example :
using System;
using System.Web.Security;
using System.Web.UI.WebControls;
namespace CustomLoginPage
{
public class Login :
Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase
{
protected System.Web.UI.WebControls.Login loginBox;
protected override bool AllowAnonymousAccess { get { return true; }
}
protected override bool AllowNullWeb { get { return true; } }
protected void Login_Click(object sender, EventArgs e)
{
if (AuthenticateUser(loginBox.UserName, loginBox.Password))
return;
}
protected bool AuthenticateUser(string emailAddr,
string password)
{
string userName = emailAddr;
MembershipUserCollection coll =
Membership.FindUsersByEmail(emailAddr);
if (coll != null && coll.Count == 1)
{
// We're doing this to force the enumerator to give us the
// one and only item because there is no by int indexer
foreach (MembershipUser user in coll)
{
userName = user.UserName;
}
}
if (Membership.ValidateUser(userName, password))
{
FormsAuthentication.RedirectFromLoginPage(userName, true);
return true;
}
return false;
}
}
}
so please do modify it.
The one Url which i follow to perform this is :
http://www.devx.com/enterprise/Article/35068/1954
Go ahead and if you face any issues. feel free to contact me : ankurmadaan2787#live.in
The answers below are really helpful -but I'm afraid my environment is limited (WSS 3.0, shared hosting).
So I simply added this link which opens up the authentication dialog:
Sign in
(Where the Source parameter indicates the URL to redirect to upon authentication.)
Thank you.