MVC 5 user data available on all pages - asp.net

Basically I need user data to be available on all pages in the site.
I need to display some user properties in the layout, like:
[Username], [FirstName], [LastName], [Email], [IsEmailVerified],
[Phone], [LastLoginDate], and 4 more properties...
But, these aspects need to be considered:
No use of Session (I disabled it in my application).
No database interaction on each request in order to get those user properties - I want to get them properties once and store them somewhere available.
I use Forms Authentication.
Now, from a little search I made, I found this article from 2008, posted by Microsoft, about Forms Authentication: Storing Additional User Data in the Ticket, and Using a Custom Principal.
I think this is the closest approach, because now I use User.Identity.Name to get only the [Username] stored in the authentication ticket, and I love using it because it is quick, available and simple.
But I also have a few questions that bother me with this approach:
This was posted in 2008, is it still relevant for 2017? is there something better and new today?
As you can see above, I have quite a bit user properties that need to go into the auth cookie, and as I read - a lot of user data in the auth cookie is not reccomended...
Thanks for the helpers.

Yes, it's still valid. Although people will tell you that newer approaches exists that involve claims and you should possibly considef the new Identity subsystem, the old good forms authentication is still one of viable options.
As for "lot of data", it's just one of shortcommings of the forms module, it doesn't handle multiple cookies, thus, you are constrained by the 4kb limit of a single cookie. Considering the encryption and signing, this makes much less data available in the custom data section of the cookie. But still, it should still be enough to have a simple serialization of like 10 attributes.
If you want to make a half of a step forward, you can just switch to the session authentication module, a benefit is that it is based on claims plus it supports large user data (as it automatically splits the data into chunks). I've blogged on that some time ago
http://www.wiktorzychla.com/2014/11/forms-authentication-revisited-for-net.html
A snippet from this approach
var identity = new ClaimsIdentity( "custom" );
identity.AddClaim( new Claim( ClaimTypes.Name, txtLogin.Text ) );
var principal = new ClaimsPrincipal( identity );
principal.AddClaim( new Claim( ClaimTypes.UserData, "whatever goes here" ) );
SessionAuthenticationModule sam =
FederatedAuthentication.SessionAuthenticationModule;
var token =
sam.CreateSessionSecurityToken( principal, string.Empty,
DateTime.Now.ToUniversalTime(), DateTime.Now.AddMinutes(20).ToUniversalTime(), false );
sam.WriteSessionTokenToCookie( token );

Related

Adding a button for google signin using f#/fable/asp.net/react

I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.

Paypal Processing - Need to grab TransactionId, CorrelationId and TimeStamp

Current Project:
ASP.NET 4.5.2
MVC 5
PayPal API
I am using this example to build myself a PayPal transaction (and yes, my code is virtually identical), as I do not know of any other method that will return the three values in the title.
My main problem is that, the example I am utilizing is much more concise and compact than the one I used for a much older Web Forms application, and as such, I am unsure as to where or even how to grab the three values I need.
My initial thought was to do so right after the ACK, and indeed I was able to obtain the CorrelationId as well as the TimeStamp, but because this was prior to the user being carted off to PayPal’s site (sandbox in this case -- see the return new PayPalRedirect contained within the if), the TransactionId was blank. And in this example, PayPal explicitly redirects the user to a Success page without returning to the Action that sent the user to PayPal in the first place, and I am not seeing any GET values in the URL at all aside from the Token and the PayerId, much less ones that could provide me with the TransactionId.
Suggestions?
I have also looked at the following examples:
For ASP.NET Core, was unsure how to adapt to my current project particularly due to appsettings.json, but it looked quite well done. I really liked how the values were rolled up in lists.
For MVC 4, but I couldn’t find where ACK was being used to determine success or successwithwarning so I couldn’t hook into that.
I have also found the PayPal content to be like trying to drink from a fire hose at full blast -- not only was the content was hopelessly outdated (Web Forms code, FTW!) but there was also so many different examples it would have taken me days to determine which one was most appropriate to use.
Any assistance would be greatly appreciated.
Edit: my initial attempt at modifying the linked code has this portion:
values = Submit(values);
var ack = values["ACK"].ToLower();
if(ack == "success" || ack == "successwithwarning") {
using(_db = new ApplicationDbContext()) {
var updateOrder = await _db.Orders.FirstOrDefaultAsync(x => x.OrderId == order.OrderId);
if(updateOrder != null) {
updateOrder.OrderProcessed = false;
updateOrder.PayPalCorrelationId = values["CORRELATIONID"];
updateOrder.PayPalTransactionId = values["TRANSACTIONID"];
updateOrder.PayPalTimeStamp = values["TIMESTAMP"];
updateOrder.IPAddress = HttpContext.Current.Request.UserHostAddress;
_db.Entry(updateOrder).State = EntityState.Modified;
await _db.SaveChangesAsync();
}
}
return new PayPalRedirect {
Token = values["TOKEN"],
Url = $"https://{PayPalSettings.CgiDomain}/cgi-bin/webscr?cmd=_express-checkout&token={values["TOKEN"]}"
};
}
Everything within and including the using() is my added content. As I mentioned, the CorrelationId and the TimeStamp come through just fine, but I have yet to successfully obtain the TransactionId.
Edit 2:
More problems -- the transactions that are “successful” through the sandbox site (the ReturnUrl is getting called) aren’t reflecting properly on my Facilitator and Buyer accounts, even when I do payments straight from the buyer’s PayPal account (not using the Credit Card). I know I am supposed to see transactions in the Buyer’s account, either through the overall Dev account (Accounts -> Profile -> balance or Accounts -> Notifications) or through the Buyer’s account in the sandbox front end. And yet -- multiple transactions returning me to the ReturnUrl path, and yet no transactions in either.
Edit 3:
Okay, this is really, really weird. I have gone over all settings with a fine-toothed comb, and intentionally introduced errors to see where things should crap out. It turns out that the entire process goes swimmingly - except nothing shows up in my notifications and no amounts get moved between my different accounts (Facilitator and Buyer). It’s like all my transactions are going into /dev/null, yet the process is successful.
Edit 4: A hint!
In the sandbox, where Buyer accepts the transaction, there is a small note, “You will be able to review the transaction before completing it” or something like that -- suggesting that an additional page is not coming up and that the user is being uncerimoniously dumped back to the success page. Why the success page? No clue. But it’s happening.
It sounds like you are only doing the first part of the process.
Express Checkout consists of 3 API calls:
SetExpressCheckout
GetExpressCheckoutDetails
DoExpressCheckoutPayment
SEC generates a token, and then you redirect to PayPal where the user signs in and reviews the transactions before agreeing to pay.
They are then sent to the ReturnURL included in your SEC request, and this is where you'll call GECD in order to obtain all the buyer details that are now available since they signed in.
Using that data you can complete the final DECP request, which is what finalizes the procedure. No money is actually processed until this final call is completed successfully.

Membership.GetAllUsers in ASP.Net

I am building a User Management page and am trying to retrieve both the number of registered users and the number currently online using the following code.(FYI...I am using mySQL as the membership provider) There are currently 4 users in the asp_net_users table so at least 4 should come up on the GetAllUsers request.
lblOnlineUsers.Text = Membership.GetNumberOfUsersOnline().ToString()
lblTotalUsers.Text = Membership.GetAllUsers.Count.ToString()
The labels are always blank. Even if I put a MsgBox(Membership.GetAllUsers.Count.ToString()) in the pageload, that msgbox never comes up. Any thoughts on what I'm doing wrong here?
You valid your user credential with ValidateUser or UpdateUser method , and you re-test

ASPNET : Switch between Session State Providers ?‏

I want to develop a new custom session state provider or use an existing (distributed caching, sql ...). Our main website renders more than 10 000 000 visits per day.It is really important for us to provide an easy rollback/switch in case of an error or a performance hit. Change web.config is not optimal because we have more than 20 front end servers. Our idea is to switch between session provider (from our custom to InProc) with a simple config in database.
Is it possible to have multiple session state providers or easily switch between providers ?
i found here http://netpl.blogspot.fr/2007/06/wrapped-inprocsessionstatestore.html, a solution for having a generic wrapper, but it does not seems quite robust.
Thanks,
Just for future references, it is not possible to dynamically change the session state provider.
it is not.. totally true.. you can do it like this using Reflection:
var privateFieldFlags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
//Get session state section
var sessionStateSection = ConfigurationManager.GetSection("system.web/sessionState") as SessionStateSection;
var values = typeof(ConfigurationElement).GetField("_values", privateFieldFlags).GetValue(sessionStateSection);
var entriesArray = values.GetType().BaseType.GetField("_entriesArray", privateFieldFlags).GetValue(values);
//Get "Mode" entry (index: 2)
var modeEntry = (entriesArray as System.Collections.ArrayList)[2];
var entryValue = modeEntry.GetType().GetField("Value", privateFieldFlags).GetValue(modeEntry);
//Change entry value to InProc
entryValue.GetType()
.GetField("Value", privateFieldFlags)
.SetValue(entryValue, System.Web.SessionState.SessionStateMode.InProc);
references:
Dynamic session state provider
http://www.answerandquestion.net/questions/4447903/dynamic-session-state-provider

Best way to persist data locally for a user on a webpage?

I have a search box on the page (webservice-fed results) and I'd like to save the search TERMS for the user in a UL\LI list on the page. So the next time they come back to the page, the results are still there....but if they clear their cache then it gets reset.
What's the best way to go about that?...I can persist between postbacks easily, but this is a new one for me.
Thanks,
Steve
Probably use a cookie, but remember you're limited to 4kb of data.
Otherwise, store the session in a database. Then save that records ID to a cookie. That way you can load the data from the database based on the ID in the cookie. Then just flush any entries in DB older than say, 30 days or something.
Due to lack of sleep, I have given you an answer in PHP. Sorry about that, I'll leave it because the information is still correct, just the syntax will be slightly different in asp.net
You have two options for data-persistence in php; cookies and sessions.
Sessions are server-side, and last as long as the browser window stays open.
Cookies are client-side, and last until the user clears their cache.
So it sounds like you want a cookie option. So in your search query processor, add the line
setcookie('search_' . time(), $_POST['search_query'], (time() + 10368000));
This will create a cookie on the client machine, with the name search_xxxx where xxxx is a timestamp (each cookie has to have a unique name otherwise they will overwrite eachother).
The weird looking calculation at the end is an expire time, which is set to 120 days in the future.
Then in your php document that displays your search page, you need to spit out all these cookie values.
foreach($_COOKIES as $k => $v) {
if(substr($k, 0, 7) == 'search_') echo($v . '<br />');
}
This will spit out each of the search terms found on the clients machine. The if statement is to make sure it only displays search term cookies, and no others.
Use a cookie. Assuming you start with a List<string> of search terms called terms, do:
var sb = new StringBuilder();
foreach (var t in terms) sb.Append(t).Append(";")
var c = new HttpCookie("terms");
c.Value = sb.ToString().TrimEnd(';');
c.Expires = DateTime.Now.AddDays(30);
Response.Cookies.Add(aCookie);
Then when you need to access those terms again (to databind to a Repeater, or process in some other way for display on your page):
if (Request.Cookies["terms"] != null) {
var terms = new List<string>();
foreach (var t in Request.Cookies["terms"].Value.Split(';')) list.Add(t);
}
A Cookie is probably your solution for today, but HTML5 localStorage will eventually be the best bet. Only supported by modern browser versions right now, depends on your users.

Resources