As somewhat of a continuation of this question, I'm having problems with dotnetopenauth.
I navigate to my relying party code and create the request, however when my provider receives the request OpenIdProvider.GetRequest() returns null. I went through the code and as far as I can tell, this is because the openid payload (request.form) is not being delivered by my relying party; but I can't figure out why this is.
Code:
Relying Party:
public ActionResult Authenticate(string RuserName = "")
{
UriBuilder returnToBuilder = new UriBuilder(Request.Url);
returnToBuilder.Path = "/OpenId/Authenticate";
returnToBuilder.Query = null;
returnToBuilder.Fragment = null;
Uri returnTo = returnToBuilder.Uri;
returnToBuilder.Path = "/";
Realm realm = returnToBuilder.Uri;
var response = openid.GetResponse();
if (response == null) {
if (Request.QueryString["ReturnUrl"] != null && User.Identity.IsAuthenticated) {
} else {
string strIdentifier = "http://localhost:3314/User/Identity/" + RuserName;
var request = openid.CreateRequest(
strIdentifier,
realm,
returnTo);
var fetchRequest = new FetchRequest();
request.AddExtension(fetchRequest);
request.RedirectToProvider();
}
} else {
switch (response.Status) {
case AuthenticationStatus.Canceled:
break;
case AuthenticationStatus.Failed:
break;
case AuthenticationStatus.Authenticated:
//log the user in
break;
}
}
return new EmptyResult();
}
Provider:
public ActionResult Index()
{
IRequest request = OpenIdProvider.GetRequest();
if (request != null) {
if (request.IsResponseReady) {
return OpenIdProvider.PrepareResponse(request).AsActionResult();
}
ProviderEndpoint.PendingRequest = (IHostProcessedRequest)request;
return this.ProcessAuthRequest();
} else {
//user stumbled on openid endpoint - 404 maybe?
return new EmptyResult();
}
}
public ActionResult ProcessAuthRequest()
{
if (ProviderEndpoint.PendingRequest == null) {
//there is no pending request
return new EmptyResult();
}
ActionResult response;
if (this.AutoRespondIfPossible(out response)) {
return response;
}
if (ProviderEndpoint.PendingRequest.Immediate) {
return this.SendAssertion();
}
return new EmptyResult();
}
Logs:
RP: 1) http://pastebin.com/Pnih3ND7 2) http://pastebin.com/eBzGun9y
Provider: http://pastebin.com/YAUTBzHk
Interestingly enough the RP log says that localhost is untrusted...yet I added it to the whitelisted hosts in my web.config, and it was "working" yesterday...
EDIT: Okay, this is weird. Yesterday I was stepping through the DNOA source trying to find out what the problem is. I enabled log4net and it created the log file and left it blank. Today I set up log4net again - it logged fine but I had an error that didn't make sense (see above). I also wasn't able to step into the DNOA source. I removed and re-added the reference to dotnetopenauth.dll, and then my "original error" with the whitelisted hosts went away, I was able to step into the source, but the log file was blank again. And I stil have the problem with request.form not being populated...
EDIT2: Both my controllers are named "OpenIdController" (both on the RP and EP). My RP is running on localhost:1903, and my endpoint is running on localhost:3314.
EDIT3: After I made the changes you suggested things started working. The RP performs the discovery fine, but I have an issue when it actually makes the request.
The line IRequest i_request = OpenIdProvider.GetRequest(); works fine, but when I try to cast it: IAuthenticationRequest iR = (IAuthenticationRequest)i_request; it gives me the following error:
System.InvalidCastException was unhandled by user code
Message=Unable to cast object of type 'DotNetOpenAuth.OpenId.Provider.AutoResponsiveRequest' to type 'DotNetOpenAuth.OpenId.Provider.IAuthenticationRequest'.
Source=Portal
StackTrace:
at Portal.Controllers.OpenIdController.Index() in Controllers\OpendIdController.cs:line 35
at lambda_method(Closure , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.<BeginInvokeSynchronousActionMethod>b__41()
at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.<BeginInvokeActionMethodWithFilters>b__33()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
This code is a bit of a hodge-podge between the two samples I found relevant. I want to set up an SSO type environment so the majority of the code I'm using is from \DotNetOpenAuth-4.1.0.12182\Samples\OpenIdWebRingSsoProvider\Code\Util.cs (ProcessAuthenticationChallenge function). However, since that function expects an IAuthenticationRequest but OpenIdProvider.GetRequest returns an AutoResponsiveRequest I figured I'd be able to cast it in order to use the properties and methods of the IAuthenticationRequest class. Obviously I was incorrect.
I'm not quite sure how to approach things at this point. Should I be using the sample code from the OpenIdProviderMVC sample? The key thing is that the login work like a single sign on, and the user is never actually prompted to enter an OpenId. I will only ever have one endpoint as well (although I will have multiple RP's).
Here's the link to the most recent RP logs: http://pastebin.com/enpwYqq3
EDIT4: I did what you suggested, and made some progress. My EP recieves the response and processes it as far as I can tell, but when it redirects back to the realm url it errors out.
012-10-10 13:55:01,171 (GMT-4) [25] ERROR DotNetOpenAuth.Messaging - Protocol error: An HTTP request to the realm URL (http://localhost:1903/) resulted in a redirect, which is not allowed during relying party discovery.
What exactly is the function of the Realm as opposed to the ReturnTo? Using the sample code, the Realm ends up being http://localhost:1903/ and the ReturnTo ends up being http://localhost:1903/OpenId/Authenticate which seems fine. Why does the EP need to make a request to the realm? I'd have thought that it should simply be sending the assertion to the returnTo once it finished processing. If I manually set the Realm to http://localhost:1903/OpenId/Authenticate then relyingParty.GetResponse() returns null.
I do have my application set up to redirect when someone accesses the base url (http://localhost:1903) - what code should I have running there to intercept the DNOA EP request?
New Logs:
RP: http://pastebin.com/L9K5Yft4
EP: http://pastebin.com/kBPWiUxp
I've also updated the code at the beginning of the question to better reflect the changes I've made.
EDIT5: Does the realm have to be the actual base URL of the application? That is, (http://localhost:1903)? Given the existing architecture in place it is difficult to remove the redirect - I tried setting the realm to the base OpenId controller (http://localhost:1903/OpenId) and testing manually did generate the XRDS document. However, the application seems to freeze, and the EP log reveals the following error:
2012-10-10 15:17:46,000 (GMT-4) [24] ERROR DotNetOpenAuth.OpenId - Attribute Exchange extension did not provide any aliases in the if_available or required lists.
Your RP has very suspiciously odd code. While it is normal (in fact required) for the return_to and realm to both have the same Uri authority, the fact that the user-supplied identifier that you're passing in as the first parameter to OpenIdRelyingParty.CreateRequest has the same host and port as your relying party is very odd. Normally the identifier you pass in would be a URL hosted by the provider. Now, I don't know if port 3314 is your RP or your OP, but either way, one of these port numbers in your RP code looks wrong.
Secondly, discovery on the user identifier fails with a null reference exception (according to v2 of your RP logs). That would prevent the login request from ever reaching your Provider. The fact that your Provider is getting called but with a non-existent OpenID request suggests that http://localhost:3314/OpenId/ is actually your OP Endpoint (the URL of your OpenID Provider's action method that reads OpenID requests). That would be inappropriate. The URL you should pass to your OpenIdRelyingParty.CreateRequest method's first parameter should, again, be a user's OpenID URL -- not an OP Endpoint. Check out the OpenIdProviderMvc sample's User controller for an example of how to set up a user OpenID URL. Then use that URL as the first arg to CreateRequest and I think you'll be good.
Thirdly, once your Provider receives a non-null request, you can't always cast it to IAuthenticationRequest. Not all OpenID messages are authentication messages. Some are part of the underlying OpenID protocol. If you look at the OpenIdProviderMvc sample's OpenID controller, you should notice that there are conditional casts to deal with the different message types. You should have similar message handling in your controller.
Since you're going for the SSO scenario, the significant difference in your controller would presumably be:
your controller never responds with a redirect to a login page, but rather "magically" figures out who the user is.
your controller should check the IAuthenticationRequest.Realm property against a whitelist of the RPs included in your SSO web ring, and only provide positive assertions when the Realm qualifies. This mitigates the attack where once your server is set up, anyone could set up a site that quietly uses your OpenID Provider to identify a user of a random Internet web site if they belong to your org, which would violate their privacy.
Fourthly, the HTTP request that the OP is sending to your RP's "realm" URL is part of a process OpenID calls "RP discovery". It's there to mitigate "open redirector" attacks. You should adjust your RP's base URL to not redirect, but rather return an XRDS document when the RP discovery request comes in. You can still redirect for the normal browser case. You can see an example of how to do this in the OpenIdRelyingPartyMvc sample's HomeController.
As you can see from the relying party log:
ERROR DotNetOpenAuth.Messaging - Protocol error: The URL 'http://localhost:3314/OpenId/' is rated unsafe and cannot be requested this way.
Your Provider is hosted on localhost, which on a production server is not a safe OpenID. So by default localhost is disabled. You can allow it for local testing by adding localhost to your whitelist by adding this to your web.config file (with the appropriate configSections at the top):
<dotNetOpenAuth>
<messaging>
<untrustedWebRequest>
<whitelistHosts>
<add name="localhost" />
</whitelistHosts>
</untrustedWebRequest>
</messaging>
</dotNetOpenAuth>
Related
I have following requirement:
the user comes to a job page in our customer's website, but the job is already taken, so the page does not exist anymore
the user should NOT get a 404 but a 410(Gone) and then be redirected to a job-overview-page where he gets the information that this job is not available anymore and a list of available jobs
but instead of a 302(temp. moved) or a 404(current behavior) google should get a 410(gone) status to indicate that this page is permanently unavailable
so the old url should be removed from the index and the new not be treated as a replacement
So how i can redirect the user with a 410 status? If i try something like this:
string overviewUrl = _urlContentResolver.GetAbsoluteUrl(overviewPage.ContentLink);
HttpContext context = _httpContextResolver.GetCurrent();
context.Response.Clear();
context.Response.Redirect(overviewUrl, false);
context.Response.StatusCode = 410;
context.Response.TrySkipIisCustomErrors = true;
context.Response.End();
I get a static error page in chrome with nothing but:
The page you requested was removed
But the status-code is correct(410) and also the Location is set correctly, just no redirect.
If i use Redirect and set the status before:
context.Response.StatusCode = 410;
context.Response.Redirect(overviewUrl, true); // true => endReponse
the redirect happens but i get a 302 instead of the desired 410.
Is this possible at all, if yes, how?
I think you're trying to bend the rules of http. The documentation states
The HyperText Transfer Protocol (HTTP) 410 Gone client error response code indicates that access to the target resource is no longer available at the origin server and that this condition is likely to be permanent.
If you don't know whether this condition is temporary or permanent, a 404 status code should be used instead.
In your situation either 404 or 410 seems to be the right status code, but 410 does not have any statement about redirection as a correct behavior that browsers should implement, so you have to assume a redirect is not going to work.
Now, to the philosophically right way to implement your way out of this...
With your stated requirements, "taken" does not mean the resource is gone. It means it exists for the client that claimed it. So, do you 302 Redirect a different client to something else that might be considered correct? You implemented that, and it seems like the right way to do it.
That said, I don't know if you "own" the behavior across the client and server to change the requirements to this approach. Looking at it from the "not found" angle, a 404 also seems reasonable. It's not found because "someone" already has the resource.
In short if your requirements are set in stone, they may be in opposition to the HTTP spec. If you still must have a 410 then you would need to change the behavior on the client-side somehow. If that's JavaScript, you'd need to expect a 410 from the server that returns a helpful payload that the client interprets to do something else (e.g. like a simulated redirect).
If you don't "own" the client code... well that's a different problem.
There's a short blog post by Tommy Griffth that backs up what I am saying. Take a read. It says in part,
The “Gone” error response code means that the page is truly gone—it’s no longer available on the origin server and no redirect was set up.
Sometimes, webmasters want to be very explicit to Google and other search engines that a page is gone. This is a much more direct signal to Google that a page is truly gone and never coming back. It's slightly more direct than a 404.
So, is it possible? Yes, but you're going to need to "fake" it by changing both client and server code.
I will accept Kit's answer since he's right in general, but maybe i have overcomplicated my requirement a bit, so i want to share my solution:
What i wanted actually?
provide crawlers a 410 so that the taken job page is delisted from search engine indexes
provide the user a better exeprience than getting a 404, so redirect him to a job-overview where he can find similar jobs and gets a message
These are two separate requirements and two separate users, so i could simply provide a solution for a crawler and one for a "normal" user.
In case someone needs something similar i can provide more details, just a snippet:
if (HttpContext.Current.IsInSearchBotMode())
{
Deliver410ForSearchBots(HttpContext.Current);
}
else
{
// redirect(301) to job-overview, omitting details
}
private void Deliver410ForSearchBots(HttpContext context)
{
context.Response.Clear();
context.Response.StatusCode = 410;
context.Response.StatusDescription = "410 job taken";
context.Response.TrySkipIisCustomErrors = true;
context.Response.End();
}
public static bool IsInSearchBotMode(this HttpContext context)
{
ISearchBotConfiguration configuration = ServiceLocator.Current.GetInstance<ISearchBotConfiguration>();
string userAgent = context.Request?.UserAgent;
return !(string.IsNullOrEmpty(userAgent) || configuration.UserAgents == null)
&& configuration.UserAgents.Any(bot => userAgent!.IndexOf(bot, StringComparison.InvariantCultureIgnoreCase) >= 0);
}
These user-agents i have used for the crawler detection:
<add key="SearchBot.UserAgents" value="Googlebot;Googlebot-Image;Googlebot-News;APIs-Google;AdsBot-Google;AdsBot-Google-Mobile;AdsBot-Google-Mobile-Apps;DuplexWeb-Google;Google-Site-Verification;Googlebot-Video;Google-Read-Aloud;googleweblight;Mediapartners-Google;Storebot-Google;LinkedInBot;bitlybot;SiteAuditBot;FacebookBot;YandexBot;DataForSeoBot;SiteCheck-sitecrawl;MJ12bot;PetalBot;Yeti;SemrushBot;Roboter;Bingbot;AltaVista;Yahoobot;YahooCrawler;Slurp;MSNbot;Lycos;AskJeaves;IBMResearchWebCrawler;BaiduSpider;facebookexternalhit;XING-contenttabreceiver;Twitterbot;TweetmemeBot" />
I have been designing an application which is just a statically served client page designed to use bearer tokens to authenticate with the backing API, however recently I have been trying to add social login options to the back-end but have found it very difficult to find any examples not using MVC dependencies which I would like to avoid if possible.
This question was a great help to get started: ASP.NET Web API social authentication for Web and Mobile
However I have been struggling to get my project to work in the same manor, basically in the question I referenced he has configured a OAuthAuthorizationServerOptions.AuthorizeEndpointPath like this:
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/account/externallogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
//AllowInsecureHttp = false
};
Also in his backing api account controller he has the following action:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider, string error = null)
In this example I have not been able to figure out what the first parameter of the RouteAttribute (template) is actually referencing in the project, if anything, could someone maybe explain what it is doing in this context?
Now when running the sample project provided in the question sending a GET request to 'api/Account/ExternalLogin' the request will be handled on the action in his API account controller and I assume it has something to do with OverrideAuthentication but am getting a little out of my depth here and struggling to find strong examples of other usages of this attribute.
However I am fairly certain I have configured my WebAPI project correctly in the way he has described, however when sending a GET request to my OAuthAuthorizationServerOptions.AuthorizeEndpointPath it is not handled on my API account controller but instead by my implementation of OAuthAuthorizationServerProvider which returns a 'invalid_request' error.
Can anyone think of something that I might be overlooking which is causing my API account controller action to be ignored?
I also had a read through this article but it seems to have been written in an older version of WebAPI:
https://thompsonhomero.wordpress.com/2015/01/21/creating-a-clean-web-api-2-project-with-external-authentication-part-2/
Thanks for any help,
Alex.
Without actually seeing your GET requests that are being made, I can only assume that they do not meet expectations by the OAuth provider.
The provider first validates the request being made, THEN it hands control over to the endpoint's controller. Your code is most likely correct, it's just that the request is malformed.
I made a new project and was able to replicate the issue you describe by making a get request to the AuthorizeEndpointPath. Unfortunately, there's not much to go off of as to why, however if you decompile source, or manage to find the source, you can see what's going on here.
Decompiling the calling code of ApplicationOAuthProvider.ValidateClientRedirectUri I get:
await this.Options.Provider.ValidateClientRedirectUri(clientContext);
if (!clientContext.IsValidated)
{
LoggerExtensions.WriteVerbose(this._logger, "Unable to validate client information");
flag = await this.SendErrorRedirectAsync(clientContext, (BaseValidatingContext<OAuthAuthorizationServerOptions>) clientContext);
}
else
{
OAuthValidateAuthorizeRequestContext validatingContext = new OAuthValidateAuthorizeRequestContext(this.Context, this.Options, authorizeRequest, clientContext);
if (string.IsNullOrEmpty(authorizeRequest.ResponseType))
{
LoggerExtensions.WriteVerbose(this._logger, "Authorize endpoint request missing required response_type parameter");
validatingContext.SetError("invalid_request");
}
else if (!authorizeRequest.IsAuthorizationCodeGrantType && !authorizeRequest.IsImplicitGrantType)
{
LoggerExtensions.WriteVerbose(this._logger, "Authorize endpoint request contains unsupported response_type parameter");
validatingContext.SetError("unsupported_response_type");
}
else
await this.Options.Provider.ValidateAuthorizeRequest(validatingContext);
if (!validatingContext.IsValidated)
{
flag = await this.SendErrorRedirectAsync(clientContext, (BaseValidatingContext<OAuthAuthorizationServerOptions>) validatingContext);
}
else
{
this._clientContext = clientContext;
this._authorizeEndpointRequest = authorizeRequest;
OAuthAuthorizeEndpointContext authorizeEndpointContext = new OAuthAuthorizeEndpointContext(this.Context, this.Options, authorizeRequest);
await this.Options.Provider.AuthorizeEndpoint(authorizeEndpointContext);
flag = authorizeEndpointContext.IsRequestCompleted;
}
}
In this code, you can see that if the request has been validated and the request's specified ResponseType is not provided, it set's the context's error to "invalid_request".
I was able to get the request to go through to the ExternalLogin controller method successfully using the following request URI:
http://localhost:18086/api/Account/ExternalLogin?provider=none&client_id=self&redirect_uri=http://localhost:18086/&response_type=token`
P.S. As far as the route attribute on the controller, the "template" field specifies the string that will be used as a template to match incoming request URIs against to determine where the request should be routed.
P.P.S. Actual source code for the decompiled snippet can be found here
I have 2 ASP.NET MVC 3 applications. I am using impersonation via the web.config to allow me to query Active Directory to get details on the user. The application uses Windows authentication and does not allow anonymous users. One application is the primary application where the user performs their tasks. The other allows the user to set up other user's to look like them in application one.
The test user's are getting the following error:
SQL1092N "<DOMAIN ID>" does not have the authority to perform the requested command.
This happens after I send a web request from my primary application to the secondary one. To get that working I had to make the request impersonate the actual user and not the identity the application uses for impersonation. This is actually an SO question I posted and had answered. That's here: How do I call an MVC Action via a WebRequest and validate the request through Active Directory?
At the end of that code, I call:
impersonationContext.Undo();
It is after this web request takes place, that the primary application tries accessing the database and now it seems that the above call has undone the impersonation of the application, so the user's attempt to do anything that opens a database connection fails. At least, that's my working theory after a day of head bashing.
My question is, how can I get the impersonation of the application to revert back to the user in the web.config? Or, when making my web request, is there a way to ensure the impersonation context only applies to that request?
The whole point of all of this is that the second application has its own sql server database. The primary application uses DB2. I would like to write the database access code once, but use it in both applications. Currently that's what I've done, but my method of relying on the web request to get the data may not be the best approach.
I'm open to any thoughts, comments, suggestions, and/or criticism. How should I go about handling this?
Okay...my theory that the IPrincipal context was changed when making the web request proved accurate, which made this fix extremely easy. Best part is, I can keep using the api I built to make this request without duplicating the Sql Server Entity Framework parts.
I have the following call to my api library:
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
This code is being called by an authorization filter attribute. The method prototype looks like
public void OnAuthorization(AuthorizationContext filterContext)
Internally, the call makes the GetProxies method following call:
public static StreamReader GetWebRequestStream(
string url,
string contentType,
bool useDefaultCredentials,
IPrincipal user)
{
var impersonationContext = ((WindowsIdentity)user.Identity).Impersonate();
var request = WebRequest.Create(url);
try
{
request.ContentType = contentType;
//request.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
//request.UseDefaultCredentials = useDefaultCredentials;
//IWebProxy p = new WebProxy();
//request.Proxy = p.
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
var response = (HttpWebResponse)request.GetResponse();
return new StreamReader(response.GetResponseStream());
}
catch (Exception e)
{
impersonationContext.Undo();
throw e;
}
finally
{
impersonationContext.Undo();
}
}
When the calling method returns, the identity of user is no longer that of the one set for the application to
impersonate. The fix is pretty simple:
//Track current identity before proxy call
IPrincipal user = context.User;
proxyRequestResultDetails = ProxyApiWrapper.GetProxies(
adUserInfo.AssociateId,
context.User);
//Undo any impersonating done in the GetProxies call
context.User = user;
2 lines of code resolved 12 hours of head ache. It could have been worse. Anyhow. Thanks for being a sounding board. I tried
having this conversion with the duck, but the duck got confused.
I'm working on an OpenId Provider for an SSO setup - it's basically a web application portal that shares credentials with any of the "applications" the user has access to. I have the Provider set up and everything is working fine, but I have a question about security.
I want to do some permissions checking on the Provider before it sends a positive assertion to the RP; namely that the user actually has permissions to the application which is making the request.
Here's the Provider code I've got at the moment (just a snippet, can add more if necessary):
private bool AutoRespondIfPossible(out ActionResult response)
{
if (ProviderEndpoint.PendingRequest.IsReturnUrlDiscoverable(OpenIdProvider.Channel.WebRequestHandler) == RelyingPartyDiscoveryResult.Success
&& User.Identity.IsAuthenticated && this.RealmIsValid(ProviderEndpoint.PendingAuthenticationRequest.Realm)) {
if (ProviderEndpoint.PendingAuthenticationRequest != null) {
if (ProviderEndpoint.PendingAuthenticationRequest.IsDirectedIdentity
|| this.UserControlsIdentifier(ProviderEndpoint.PendingAuthenticationRequest)) {
ProviderEndpoint.PendingAuthenticationRequest.IsAuthenticated = true;
response = this.SendAssertion();
return true;
}
}
//we don't want anon requests
if (ProviderEndpoint.PendingAnonymousRequest != null) {
ProviderEndpoint.PendingAnonymousRequest.IsApproved = false;
response = this.SendAssertion();
return true;
}
}
response = null;
return false;
}
Basically what I'm doing is validating that the realm of the request (in the RealmIsValid method) matches to a hostname in my list of acceptable hostnames, and then I'm comparing the user permissions based on the hostname.
What I'm wondering is: How accurate is ProviderEndpoint.PendingAuthenticationRequest.Realm? If I understand correctly, the realm is set by the relying party - is it possible that the endpoint could receive a request from a URI other than the realm specified in that request? Or am I safe to assume that the realm will always be accurate (that is: match the URI of the relying party)?
Yes, the OpenID realm is reliable, due to two steps OpenID 2.0 and DotNetOpenAuth takes:
The OpenID return_to URL must be a derivative of the realm URL. So while anyone can formulate an OpenID request as if it came from any relying party, the alleged relying party will always be the one to actually receive the response, so an attacker operating another RP will not get the response.
Some "open redirector" attacks might allow the attacker to use a return_to URI that is based on a legitimate Realm URL, but happens to be a URL that will redirect to the attacker's web site, thus delivering the assertion to the attacker. This is mitigated by "RP Discovery" which your code snippet includes with its call to the IsReturnUrlDiscoverable method. The RP should explicitly list the allowed return_to URLs in its RP Discovery XRDS document, so that open redirector endpoints are not allowed.
That all said, OpenID is mostly about identifying the user -- not authorizing them to specific RPs. So while what you're doing may be fine, it's a bit off the beaten track for OpenID use, so please consider the security implications carefully (as it sounds like you're doing now).
This question is, I think, similar to my previous one.
Using the latest C# Facebook SDK on .NET 4 I get an Exception with the message "ClientID required" with the following code on the last line:
var app = new DefaultFacebookApplication();
app.AppId = "appId";
app.AppSecret = "secret";
var fb = new FacebookWebContext(app);
fb.IsAuthenticated();
App ID and secret are properly set. The stack trace of the exception is the following:
System.Exception occurred
Message=ClientID required. Source=Facebook StackTrace:
at Facebook.FacebookOAuthClient.BuildExchangeCodeForAccessTokenParameters(IDictionary`2 parameters, String& name, String& path)
at Facebook.FacebookOAuthClient.ExchangeCodeForAccessToken(String code, IDictionary`2 parameters)
at Facebook.FacebookSession.get_AccessToken()
at Facebook.FacebookSession.get_Expires()
at Facebook.Web.FacebookWebContext.IsAuthenticated()
at Piedone.FacebookTest.Authorize() InnerException:
On the client side I'm using the JS SDK, initialized as following:
FB.init({
appId: appId,
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true, // parse XFBML
oauth: true // enable OAuth 2.0
});
The users gets properly logged in with the JS login() method, as the alert in the following piece of code runs:
FB.login(function (response) {
if (response.authResponse) {
alert("logged in");
} else {
alert('User cancelled login or did not fully authorize.');
}
}, { scope: scope });
In the app settings on Facebook both the "Forces use of login secret for OAuth call and for auth.login" and "Encrypted Access Token" are turned on. As far as I know all this should enable the use of the OAuth 2 authentication.
Anybody has an idea what am I doing wrong? There really can't be any error in these few lines of code...
Thanks in advance for any help!
Edit:
The AccessToken property of FacebookWebContext throws the same error and HttpContext.CurrentNotification does:
CurrentNotification '(_facebookWebContextCache.HttpContext).CurrentNotification' threw an exception of type 'System.PlatformNotSupportedException' System.Web.RequestNotification {System.PlatformNotSupportedException}
This operation requires IIS integrated pipeline mode.
Since I must run the program from Visual Studio with its Development Server (as I'm currently developing the application) there is no way anything can be done about the latter exception, I suppose. Actually I also tried with Webmatrix's IIS express, but the problem persists.
It's also interesting, that in the FacebookWebContext the settings (app id, secret) are correctly set as well, the user Id and the signed request is also there...
Edit 2:
I also get the same error when using the SDK source. It looks that AccessToken and in the Session the Expires property throw the exception. I don't know if this is connected to the httpcontext issue above.
One more solution is add facebook settings to you web or app congfig
<facebookSettings appId="appid" appSecret="secret" />
after that create Auth class
var oauth = new FacebookOAuthClient(FacebookApplication.Current);
And it wil work as well
Finally I managed to solve the problem, but most likely this is a bug in the SDK.
The cause
The problem is that the FacebookApplication.Current is empty, as it does not get populated with data set in the FacebookWebContext ctor. This leads to the problem of the access token: in FacebookSession.AccessToken on line 119 FacebookOAuthClient is instantiated with FacebookApplication.Current, that of course is practically empty. So FacebookOAuthClient is throwing the exception as it doesn't get the application settings.
The solution
The workaround is to simply explicitly set the current FacebookApplication together with the instantiation of FacebookWebContext:
var app = new DefaultFacebookApplication();
app.AppId = "appId";
app.AppSecret = "secret";
var fb = new FacebookWebContext(app);
FacebookApplication.SetApplication(app); // Note this is the new line