Is it possible to change query string arguments on AccessDeniedPath? - c#

In an ASP.NET Core 3.1 web app, I can change the target route for unauthorised requests in my Startup.cs like so:
services.ConfigureApplicationCookie(o =>
{
o.AccessDeniedPath = "/Home/Error";
});
That will return /Home/Error?ReturnUrl=... where ... is whatever page I was trying to access.
But I actually just want it to return simply "/Home/Error?code=401"
I tried e.g.
o.AccessDeniedPath = "/Home/Error?code=401"
but that brings back simply
"/Home/Error?code=401?ReturnUrl=%2FAdmin"
I then realised there's a ReturnUrlParameter in the options, like this:
o.ReturnUrlParameter = "code";
o.AccessDeniedPath = "/Home/Error";
which gets me this far in the redirect:
/Home/Error?code=%2FAdmin
But I want to specify the keyvalue value (e.g. 401), i.e. replace the page the request came from, so the final result would be
/Home/Error?code=401

According to your code, it seems that you are using cookie Authentication, I suggest yo could try to change the redirect URL using the CookieAuthenticationEvents.OnRedirectToAccessDenied Property, check the following sample code:
services.AddAuthentication("CookieAuthentication")
.AddCookie("CookieAuthentication", config =>
{
config.Cookie.Name = "UserLoginCookie"; // Name of cookie
config.LoginPath = "/Login/UserLogin"; // Path for the redirect to user login page
config.AccessDeniedPath = "/Login/UserAccessDenied";
config.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents()
{
OnRedirectToAccessDenied = ctx =>
{
var redirectPath = ctx.RedirectUri;
if (redirectPath.Contains("?ReturnUrl"))
{
//remove the ReturnURL
var url = redirectPath.Substring(0, redirectPath.LastIndexOf("?ReturnUrl"));
ctx.Response.Redirect(url + "?code=401");
}
// Or, directly using the following code:
//ctx.Response.Redirect("/Login/UserAccessDenied?code=401");
return Task.CompletedTask;
}
};
});
The output like this:

Related

async and await with Twitter direct_messages/events using LinqToTwitter

I'm really stuck in this for days. I'm using LinqToTwitter with ASP.Net C#
I'm trying to get the new DirectMessages work, I followed the examples but with no luck.
I want the function to work on Button click, so what I tried is:
BtnClick:
`
protected void Btn1_Click(object sender, EventArgs e)
{
string x = MyTest().Result;
}
`
MyTest:
`
static async Task<string> mytest()
{
AspNetAuthorizer auth = DoAuthorization();
var twitterCtx = new TwitterContext(auth);
List<DMEvent> AllDmEvents = new List<DMEvent>();
string Cursor;
DirectMessageEvents dmResponse =
await
(from dm in twitterCtx.DirectMessageEvents
where dm.Type == DirectMessageEventsType.List &&
dm.Count == 10
select dm)
.SingleOrDefaultAsync(); //In debugging mode, after this line is executed, it will go away and keep loading forever and never come back
AllDmEvents.AddRange(dmResponse.Value.DMEvents);
Cursor = dmResponse.Value.NextCursor;
string xxx = (JsonConvert.SerializeObject(AllDmEvents, Formatting.None));
return xxx;
}
`
DoAuthorization:
`
static AspNetAuthorizer DoAuthorization()
{
AspNetAuthorizer auth = new AspNetAuthorizer();
auth = new AspNetAuthorizer
{
CredentialStore = new SessionStateCredentialStore
{
ConsumerKey = "MyConsumerKey",
ConsumerSecret = "MyConsumerSecret ",
OAuthToken = "MyOAuthToken ",
OAuthTokenSecret = "MyOAuthTokenSecret ",
ScreenName = "MyUserName",
UserID = 12345678
}
};
return auth;
}`
Any help would be SO much appreciated!
The DoAuthorization() in your code looks like it came from the Console sample and that won't work with ASP.NET. The reason is that ASP.NET is stateless and the OAuth process brings you to the Twitter site and back. So, you have to break up the authorization into two pieces: Begin and Complete.
I'm guessing that you're using ASP.NET MVC, but the concept is similar (but different) if you're using WebForms). Here's the Begin part:
public class OAuthController : AsyncController
{
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> BeginAsync()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore
{
ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"]
}
};
Notice that it uses an MvcAuthorizer, populating credentials. Once you have the MvcAuthorizer instance, redirect the user to Twitter for authorization, like this:
string twitterCallbackUrl = Request.Url.ToString().Replace("Begin", "Complete");
return await auth.BeginAuthorizationAsync(new Uri(twitterCallbackUrl));
}
That send the user to the Twitter authorization page, where they give your app permission to operate on their behalf. Twitter will redirect the user back to twitterCallback, which is why the code above modified the URL to replace the Begin with Complete in your URL. So, Twitter redirect the user back to your app, which calls the CompleteAsync() action below:
public async Task<ActionResult> CompleteAsync()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore()
};
await auth.CompleteAuthorizeAsync(Request.Url);
// This is how you access credentials after authorization.
// The oauthToken and oauthTokenSecret do not expire.
// You can use the userID to associate the credentials with the user.
// You can save credentials any way you want - database,
// isolated storage, etc. - it's up to you.
// You can retrieve and load all 4 credentials on subsequent
// queries to avoid the need to re-authorize.
// When you've loaded all 4 credentials, LINQ to Twitter will let
// you make queries without re-authorizing.
//
//var credentials = auth.CredentialStore;
//string oauthToken = credentials.OAuthToken;
//string oauthTokenSecret = credentials.OAuthTokenSecret;
//string screenName = credentials.ScreenName;
//ulong userID = credentials.UserID;
//
return RedirectToAction("Index", "Home");
}
Now that your app has the user's permissions, grab their tokens and hold them for subsequent queries so you don't have to continue the OAuth process every time the user wants to use your app. Please see the notes in the code on how to get those credentials.
Now, when you want to perform a query, instantiate an MvcAuthorizer, like this:
static async Task<string> mytest()
{
var auth = new MvcAuthorizer
{
CredentialStore = new SessionStateCredentialStore()
};
var twitterCtx = new TwitterContext(auth);
List<DMEvent> AllDmEvents = new List<DMEvent>();
string Cursor;
DirectMessageEvents dmResponse =
await
(from dm in twitterCtx.DirectMessageEvents
where dm.Type == DirectMessageEventsType.List &&
dm.Count == 10
select dm)
.SingleOrDefaultAsync(); //In debugging mode, after this line is executed, it will go away and keep loading forever and never come back
AllDmEvents.AddRange(dmResponse.Value.DMEvents);
Cursor = dmResponse.Value.NextCursor;
string xxx = (JsonConvert.SerializeObject(AllDmEvents, Formatting.None));
return xxx;
}
You can see how the first statement of your modified myTest() method instantiates MvcAuthorizer with SessionStateCredentialStore, holding your credentials.
Finally, at the point in time where you want the user to authorize your app with Twitter (log in, on first query, or any other timing of your choice), check to see whether they're already authorized and re-direct if not, like this:
public ActionResult Index()
{
if (!new SessionStateCredentialStore().HasAllCredentials())
return RedirectToAction("Index", "OAuth");
return View();
}
Notice how the code above calls HasAllCredentials() on a SessionStateCredentialStore instance. I assume that you'll be adding your own logic to determine when to load the user's credentials, but wanted you to be aware of the HasAllCredentials() helper method to make it easier to know when the user must be authorized.
For more info, visit the LINQ to Twitter OAuth docs. The LINQ to Twitter source code also has Samples on how to use OAuth.

How to get the full url of the current api NOT of the original requestor

Background
I have a local backend API at this address:
http://localhost:54641/orders
And a clientside UI at:
http://localhost:3000/
What I need
The UI does a call to the backend, to list all available orders. This includes information on an attachment for each order. It may or not be there. If there is an attachment, you should get this response from the API, for each order:
{
"orderReference": "123456",
"actions": { "download": "http://localhost:54641/orders/123456/download" }
}
Actions will be {} if there's no attachment available.
However
What I do get, is this:
{
"orderReference": "123456",
"actions": { "download": "http://localhost:3000/orders/123456/download" }
}
Which doesn't exist, ofcourse.
What I have right now
Is this code to build the full url, which is going wrong:
var baseUrl = Request.RequestUri.GetLeftPart(UriPartial.Authority);
var uri = Url.Route("DownloadLabel", new {orderReference });
var fullUrl = $"{baseUrl}{uri}";
As in, it returns the requestor's full url path, not that of the current API.
Question
What can I do to get the API url in the response?
So, it should return like this:
http://localhost:54641/orders/123456/download
I guess you want this,
string fullUrl=HttpContext.Current.Request.Url.ToString();
var wantedUrl= fullUrl.SubString(0,fullUrl.IndexOf("/orders"))+"/orders/"+orderReference+"/download";
Found it, I used httpcontext instead:
var baseUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
var uri = Url.Route("DownloadLabel", new {orderReference });
var fullUrl = $"{baseUrl}{uri}";

Pass query string parameter through OpenId Connect authentication

Let me put the problem with a bit of structure.
Context
We have a web application build with Web Forms and hosted in an Azure Web App that authenticates the users against an Azure Active Directory using the OWIN + OpenId Connect standards.
The authentication process works like a charm and users are able to access the application without any problem.
So, whats the issue?
After struggling for many days with it I'm unable to pass any query string parameter to the application through the authentication process. For example, if I try to access the application for the first time through the URL: https://myapp.azurewebsites.net/Default.aspx?param=value. The reason I need to pass this parameter is that it triggers some specific actions in the main page.
The problem is that after the authentication redirects to the webapp's main page the original query string parameters of the request are gone.
The code
The startup class looks like this:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = Constants.ADTenant.ClientId,
Authority = Constants.ADTenant.Authority,
PostLogoutRedirectUri = Constants.ADTenant.PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = context =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(Constants.ADTenant.ClientId,
Constants.ADTenant.AppKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
Constants.ADTenant.ObjectIdClaimType).Value;
AuthenticationContext authContext = new AuthenticationContext(Constants.ADTenant.Authority,
new NaiveSessionCache(userObjectID));
if (HttpContext.Current != null)
{
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential,
Constants.ADTenant.GraphResourceId);
AuthenticationHelper.token = result.AccessToken;
AuthenticationHelper.refreshToken = result.RefreshToken;
}
return Task.FromResult(0);
}
}
});
And it works properly!
What I already tried
I've got access to the original request Url by adding an overwrite of the RedirectToIdentityProvider notification:
RedirectToIdentityProvider = (context) =>
{
// Ensure the URI is picked up dynamically from the request;
string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + context.Request.Uri.PathAndQuery;
context.ProtocolMessage.RedirectUri = appBaseUrl;
return Task.FromResult(0);
}
With this I tried to force the redirect to the main page including the original query string parameter, but then the redirection after authentication breaks and gets stuck in an infinite loop.
I've also tried with changing the redirect url of the application configuration in Azure AD without luck. Also tried to store the query string parameters somewhere else, but the Session is not accessible that early in the process.
Does anyone know what am I doing wrong? Or I'm just asking for something impossible? Any help would be appreciated.
Thank you very much in advance!
I recently had a need to do the exact same thing. My solution may not be the most sophisticated, but simple isn't always bad either.
I have two Authentication Filters...
The first filter is applied to all controllers that could potentially be hit with query string parameters prior to authorization. It checks if the principal is authenticated. If false it caches the complete url string in a cookie. If true it looks for any cookies present and clears them, just for cleanup.
public class AuthCheckActionFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if (!filterContext.Principal.Identity.IsAuthenticated)
{
HttpCookie cookie = new HttpCookie("OnAuthenticateAction");
cookie.Value = filterContext.HttpContext.Request.Url.OriginalString;
filterContext.HttpContext.Response.Cookies.Add(cookie);
}
else
{
if (filterContext.HttpContext.Request.Cookies.AllKeys.Contains("OnAuthenticateAction"))
{
HttpCookie cookie = filterContext.HttpContext.Request.Cookies["OnAuthenticateAction"];
cookie.Expires = DateTime.Now.AddDays(-1);
filterContext.HttpContext.Response.Cookies.Add(cookie);
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}
The second filter is applied only to the default landing page, or in other words where the identity server is redirecting after login. This second filter looks for a cookie and if it exists it calls response.Redirect on cookie value.
public class AutoRedirectFilter : ActionFilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
if(filterContext.Principal.Identity.IsAuthenticated)
{
if(filterContext.HttpContext.Request.Cookies.AllKeys.Contains("OnAuthenticateAction"))
{
HttpCookie cookie = filterContext.HttpContext.Request.Cookies["OnAuthenticateAction"];
filterContext.HttpContext.Response.Redirect(cookie.Value);
}
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
}
}

Get site url on mvc

I want to write a little helper function that returns the site url.
Coming from PHP and Codeigniter, I'm very upset that I can't get it to work the way I want.
Here's what I'm trying:
#{
var urlHelper = new UrlHelper(Html.ViewContext.RequestContext);
var baseurl = urlHelper.Content("~");
}
<script>
function base_url(url) {
url = url || "";
return '#baseurl' + url;
}
</script>
I want to return the base url of my application, so I can make ajax calls without worrying about paths. Here's how I intend to use it:
// Development
base_url(); // http://localhost:50024
// Production
base_url("Custom/Path"); // http://site.com/Custom/Path
How can I do something like that?
EDIT
I want absolute paths because I have abstracted js objects that makes my ajax calls.
So suppose I have:
function MyController() {
// ... js code
return $resource('../MyController/:id');
}
// then
var my_ctrl = MyController();
my_ctrl.id = 1;
my_ctrl.get(); // GET: ../MyController/1
This works when my route is http://localhost:8080/MyController/Edit but will fail when is http://localhost:8080/MyController .
I managed to do it like this:
#{
var url = Request.Url;
var baseurl = url.GetLeftPart(UriPartial.Authority);
}
Thank you all!
Are you aware of #Url.Action("actionname") and #Url.RouteUrl("routename") ?
Both of these should do what you're describing.
Instead of manually creating your URL's, you can use #Url.Action() to construct your URLs.
<p>#Url.Action("Index", "Home")</p>
/Home/Index
<p>#Url.Action("Edit", "Person", new { id = 1 })</p>
/Person/Edit/1
<p>#Url.Action("Search", "Book", new { title = "Gone With The Wind" })</p>
/Book/Search?title="Gone+With+The+Wind"
Now the absolute best reason to go with this option is that #Url.Action automatically applies any vanity URL routes you have defined in your Global.asax file. DRY as the sub-saharan desert! :)
In your case, your can create a 'custom path' in two ways.
Option A)
<p>#Url.Action("Path", "Custom")</p>
/Custom/Path
Option B)
You can create a route using the Global.asax file. So your controller/action combo can be anything you want, and you can create a custom vanity route url - regardless of the controller/action combo.

Facebook .NET SDK: How to authenticate with ASP.NET MVC 2

I am trying to get the grip on the Facebook SDK and at the same time transitioning from ASP.NET forms to MVC (finally). So please bear with me ..
I have created two controller actions:
FBLogon is execetued when the user clicks on the FB login button on the form.
He is then redirected to the FB login page.
Afterwards he gets sent back to the FBAuthorize page, which is supposed to parse the returned url for the access token. I get something like:
http://localhost:5000/account/FBAuthorize#access_token=199143326771791|827213759889396d5408fee6-100001815992604|BmYchAOMqSoZ2L0TYgCrtpoKP3M&expires_in=0
The problem I see, is that as the access_token is passed behind a #, asp.net cannot parse it on the server. Am I doing something fundamentaly wrong?
Code follows:
public ActionResult FBLogon()
{
var settings = ConfigurationManager.GetSection("facebookSettings");
IFacebookApplication current = null;
if (settings != null)
{
current = settings as IFacebookApplication;
if (current.AppId == "{app id}" || current.AppSecret == "{app secret}")
{
return View();
}
}
string[] extendedPermissions = new[] { "publish_stream", "offline_access" };
var oauth = new FacebookOAuthClient { ClientId = current.AppId, RedirectUri = new Uri("http://localhost:5000/account/FBAuthorize") };
var parameters = new Dictionary<string, object>
{
{ "response_type", "token" },
{ "display", "page" }
};
if (extendedPermissions != null && extendedPermissions.Length > 0)
{
var scope = new StringBuilder();
scope.Append(string.Join(",", extendedPermissions));
parameters["scope"] = scope.ToString();
}
var loginUrl = oauth.GetLoginUrl(parameters);
return Redirect(loginUrl.ToString());
}
public ActionResult FBAuthorize()
{
FacebookOAuthResult result;
if (FacebookOAuthResult.TryParse(Request.Url, out result))
{
if (result.IsSuccess)
{
var accesstoken = result.AccessToken;
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
Ok. The facebook docs say it quite clearly:
Because the access token is passed in
an URI fragment, only client-side code
(such as JavaScript executing in the
browser or desktop code hosting a web
control) can retrieve the token. App
authentication is handled by verifying
that the redirect_uri is in the same
domain as the Site URL configured in
the Developer App
from http://developers.facebook.com/docs/authentication/ ---> Client-side Flow Section.
So I'm sending the token back to my server to complete the authentication..
Update:
The sending back to the server I do using Javascript something like this:
var appId = "<%: Facebook.FacebookContext.Current.AppId %>";
if (window.location.hash.length > 0) {
accessToken = window.location.hash.substring(1);
var url = window.location.href.replace(/#/, '?');
window.location = url;
}
On the server then I have the following action. Not very nice but it works..
public ActionResult FBAuthorize()
{
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
/// hack to make FacebookOuthResult accept the token..
url = url.Replace("FBAuthorize?", "FBAuthorize#");
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string[] extendedPermissions = new[] { "user_about_me", "offline_access" };
var fb = new FacebookClient(result.AccessToken);
dynamic resultGet = fb.Get("/me");
var name = resultGet.name;
RegisterModel rm = new Models.RegisterModel();
rm.UserName = name;
rm.Password = "something";
rm.Email = "somethig";
rm.ConfirmPassword = "23213";
//Label1.Text = name;
//Response.Write(name);
//return RedirectToAction("register", "Account", rm);
ViewData["Register"] = rm;
return RedirectToAction("Register");
}
else
{
var errorDescription = result.ErrorDescription;
var errorReason = result.ErrorReason;
}
}
return View();
}
I found this post http://facebooksdk.codeplex.com/discussions/244568 on codeplex. I think this is what you need.
Note that instead of using the client-side flow, you need to use the server-side flow.
This is what you should do
Create a login link for server-side flow. After Authorization, facebook will return an url containing a code instead of a access token.
Then you request for a token from facebook using the code. this is my example
public ActionResult FBAuthorize()
{
FacebookOAuthClient cl = new FacebookOAuthClient(FacebookContext.Current);
FacebookOAuthResult result = null;
string url = Request.Url.OriginalString;
// verify that there is a code in the url
if (FacebookOAuthResult.TryParse(url, out result))
{
if (result.IsSuccess)
{
string code = result.Code;
// this line is necessary till they fix a bug *see details below
cl.RedirectUri = new UriBuilder("http://localhost:5000/account/FBAuthorize").Uri;
var parameters = new Dictionary<string, object>();
//parameters.Add("permissions", "offline_access");
Dictionary<String, Object> dict = (Dictionary<String, Object>)cl.ExchangeCodeForAccessToken(code, new Dictionary<string, object> { { "redirect_uri", "http://localhost:5000/account/FBAuthorize" } });
Object Token = dict.Values.ElementAt(0);
TempData["accessToken"] = Token.ToString();
return RedirectToAction ("ShowUser");
}
else
{
var errorDescription = result.ErrorDescription;
}
}
else
{
// TODO: handle error
}
return View();
}
*There is bug when using IIS in localhost, see the original post for details (the redirect uri when asking for the token must be the same as the one used asking for the code)
It is highly recommended to use IIS and not visual studio web server. There are many things that wont work in visual studio web server.
I am in the same spot you are at the moment.
We never get the Request.QueryString populated becasue of the "fragment" or # in the url.
Love to know if you solved this and how.
It does not look like the FacebookOAuthResult class was written to be used in web applications of any sort.
you can change the response type in you scope paramas to be "code" then it will send back a code in the querystring in which you can swap for a token.

Categories

Resources