My goal is to use the FB login button so that FB users can log into my ASP.NET MVC 3 website. It seems that things have changed recently with the Facebook C# SDK and all the old examples will not work with the new version. I've tried for a day to get them to work... I'm working off of the tutorial Getting Started with the Facebook C# SDK for ASP.NET
Currently when I browse to http://localhost:8033/ it seems to automatically log me in (even after a fresh restart of Chrome) because it shows "my-name uses my-app-name" and shows my picture. I expected it to instead show a FB login button. And when I go to http://localhost:8033/Home/About I get an error that Session["AccessToken"] is null (which makes sense because it's clearly not getting set).
Here's what I have:
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Facebook;
namespace FacebookTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
var accessToken = Session["AccessToken"].ToString();
var client = new FacebookClient(accessToken);
dynamic result = client.Get("me", new { fields = "name,id" });
string name = result.name;
string id = result.id;
ViewBag.Message = "Hello id: " + id;
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult FacebookLogin(HttpContext context)
{
var accessToken = context.Request["accessToken"];
context.Session["AccessToken"] = accessToken;
return RedirectToAction("About");
}
}
}
Index.cshtml
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({
//appId: 'YOUR_APP_ID', // App ID
appId: '<MY-NUMBER-REMOVED>', // App ID
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true // parse XFBML
});
// Additional initialization code here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if (response.status === 'connected') {
// the user is logged in and has authenticated your
// app, and response.authResponse supplies
// the user's ID, a valid access token, a signed
// request, and the time the access token
// and signed request each expire
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
// TODO: Handle the access token
// Do a post to the server to finish the logon
// This is a form post since we don't want to use AJAX
var form = document.createElement("form");
form.setAttribute("method", 'post');
//form.setAttribute("action", '/FacebookLogin.ashx');
form.setAttribute("action", '/Home/FacebookLogin');
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'accessToken');
field.setAttribute("value", accessToken);
form.appendChild(field);
document.body.appendChild(form);
form.submit();
} else if (response.status === 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK Asynchronously
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
} (document));
</script>
<div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div>
About.cshtml
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
#ViewBag.Message
</p>
Can you tell me how to fix this so that a FB login button is displayed, and when clicked it asks the users to do a FB authentication, sends them back, and then my app recognizes them as a logged in user?
As for the Login button, if you are logged in to Facebook prior to visiting your app you will see the faces instead of the login button, the only way to get the Login button back is to go to facebook.com and do a logout or possibly do a facebook logout using the C# SDK. Depending on your requirements this may or may not be what you wanted. There is a bit about Re-Authentication in the SDK documentation if that is what you really want.
I've tweaked your app by removing the submit() and replaced it with an ajax post, The FacebookLogin action was changed and I added some error handling on the About action. Your original app will work but it will automatically redirect to About if you are logged in to Facebook.
Update Added a login link which does not use Javascript, insert appid and appsecret and adjust portnumber accordingly. This was adapted from the server side login sample found here which is far prettier than this code :)
Note The state value being passed in the server side flow should be a unqiue value that you should validate in the ConnectResponse() method, i.e. generate a value in FacebookLoginNoJs and make sure it's the same in ConnectResponse to prevent cross site request forgery
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Facebook;
namespace FacebookTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
ViewBag.Message = "Please log in first";
if (Session["AccessToken"] != null)
{
var accessToken = Session["AccessToken"].ToString();
var client = new FacebookClient(accessToken);
try
{
dynamic result = client.Get("me", new { fields = "name,id" });
string name = result.name;
string id = result.id;
ViewBag.Message = "Hello id: " + id + " aka " + name;
}
catch (FacebookOAuthException x)
{
}
}
return View();
}
public void FacebookLogin(string uid, string accessToken)
{
var context = this.HttpContext;
context.Session["AccessToken"] = accessToken;
}
public ActionResult FacebookLoginNoJs()
{
return Redirect("https://www.facebook.com/dialog/oauth?client_id=MY-APPID-REMOVED&redirect_uri=http://localhost:45400/Home/ConnectResponse&state=secret");
}
public ActionResult ConnectResponse(string state, string code, string error, string error_reason, string error_description, string access_token, string expires)
{
if (string.IsNullOrEmpty(error))
{
try
{
var client = new FacebookClient();
dynamic result = client.Post("oauth/access_token",
new
{
client_id = "MY-APPID-REMOVED",
client_secret = "MY-APP-SECRET-REMOVED",
redirect_uri = "http://localhost:45400/Home/ConnectResponse",
code = code
});
Session["AccessToken"] = result.access_token;
if (result.ContainsKey("expires"))
Session["ExpiresIn"] = DateTime.Now.AddSeconds(result.expires);
}
catch
{
// handle errors
}
}
else
{
// Declined, check error
}
return RedirectToAction("Index");
}
}
}
Index.cshtml
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
</p>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function () {
FB.init({
//appId: 'YOUR_APP_ID', // App ID
appId: 'MY-APPID-REMOVED', // App ID
status: true, // check login status
cookie: true, // enable cookies to allow the server to access the session
xfbml: true // parse XFBML
});
// Additional initialization code here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if (response.status === 'connected') {
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
var url = '/Home/FacebookLogin';
$.post(url, { uid: uid, accessToken: accessToken }, function (data) {
});
} else if (response.status === 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK Asynchronously
(function (d) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
} (document));
</script>
<div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div>
#Html.ActionLink("The NoJs Login", "FacebookLoginNoJs", "Home")
About.cshtml
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
#ViewBag.Message
</p>
Related
I'm using .NET and Stripe to make a webshop, and I'm trying to figure out how to redirect a customer to a success page, once the charge has been successfull. However Stripe recently changed their API, and I've been unable to find any resources online explaining how to do this.
I've tried creating a webhook that listens to the charge.succeeded event, and I can get the event to trigger, but I'm unable to redirect the customer to any page from the webhook.
Another thing I've tried is in the checkout page where I've added method="post" to the form, and type="submit" and formmethod="post" to the button respectively, so that when the customer clicks "Pay," the customer is redirected through the post method of the checkout page, but I can't get the post method to run.
Checkout razor page:
<head>
<title>Checkout</title>
<script src="https://js.stripe.com/v3/"></script>
</head>
<--! This is where I've tried method="post", type="submit" and formmethod="post" -->
<form id="payment-form">
<div id="card-element">
<!-- Elements will create input elements here -->
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<button id="submit">Pay</button>
</form>
#section scripts{
<script>
// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = Stripe('{PUBLIC KEY}');
var elements = stripe.elements();
window.onload = function () {
// Set up Stripe.js and Elements to use in checkout form
var style = {
base: {
color: "#32325d",
}
};
var card = elements.create("card", { style: style });
card.mount("#card-element");
card.addEventListener('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
var form = document.getElementById('payment-form');
form.addEventListener('submit', function (ev) {
ev.preventDefault();
stripe.confirmCardPayment('#Model.ClientSecret', {
payment_method: {
card: card,
billing_details: {
name: '#Model.CustomerInformation.FirstName',
email: '#Model.CustomerInformation.Email',
address: {
city: '#Model.CustomerInformation.City',
line1: '#Model.CustomerInformation.Address1',
postal_code: '#Model.CustomerInformation.ZipCode'
}
}
}
}).then(function (result) {
if (result.error) {
// Show error to your customer (e.g., insufficient funds)
console.log(result.error.message);
} else {
// The payment has been processed!
if (result.paymentIntent.status === 'succeeded') {
// Show a success message to your customer
// There's a risk of the customer closing the window before callback
// execution. Set up a webhook or plugin to listen for the
// payment_intent.succeeded event that handles any business critical
// post-payment actions.
}
}
});
});
};
</script>
Webhook:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace workspace.Controllers
{
[Route("api/[controller]")]
public class StripeWebHook : Controller
{
// If you are testing your webhook locally with the Stripe CLI you
// can find the endpoint's secret by running `stripe listen`
// Otherwise, find your endpoint's secret in your webhook settings in the Developer Dashboard
const string endpointSecret = "ENDPOINT SECRET";
[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ParseEvent(json);
// Handle the event
if (stripeEvent.Type == Events.PaymentIntentSucceeded)
{
var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
Console.WriteLine("Successful!");
return Ok();
}
else if (stripeEvent.Type == Events.ChargeSucceeded)
{
Console.WriteLine("Successful!");
// This is where I've tried return RedirectToPage("/Index");
return Ok();
}
else if (stripeEvent.Type == Events.PaymentIntentCreated)
{
Console.WriteLine("Successful!");
return Ok();
}
else if (stripeEvent.Type == Events.PaymentMethodAttached)
{
var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
Console.WriteLine("PaymentMethod was attached to a Customer!");
}
// ... handle other event types
else
{
// Unexpected event type
return BadRequest();
}
return Ok();
}
catch (StripeException e)
{
return BadRequest();
}
}
}
}
I think that we should iron out a few concepts here first. The webhook endpoint is a standalone API that lives in your system somewhere and reacts to Events that are posted to it from Stripe, such as the charge.succeeded Event.
Your Elements implementation in the browser is completely separate and can't respond to anything that your webhook endpoint can return in terms of HTTP codes (redirects and such).
To answer your core question directly, in the Javascript in the else block where it says that the payment was successfully processed, you can call [0]
location.href = "https://your-success-page.com"
... to send the user to a success page. The reason that the form won't submit is because the submit event of the form has been prevented with ev.preventDefault();.
This whole flow is documented in detail here [1][2].
Hope this helps!
[0] https://developer.mozilla.org/en-US/docs/Web/API/Window/location
[1] https://stripe.com/docs/payments/accept-a-payment
[2] https://stripe.com/docs/webhooks
I've been searching through some examples here, but could not find what I needed, so I'm posting my question here:
I'm using Form authentication and MVC 4.5.
The problem is following:
I'm emailing the URL to the user and the user needs to click on URL and be able to get redirected to that URL after he was authenticated.
My Login Controller has a method Index that is invoked when user tries to log in:
[HttpPost]
public ActionResult Index(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
try
{
LoginModel lm = LoginModel.AuthenticateUser(model.UserName, model.Password);
if (lm.IsAuthenticated)
{
ApplicationSession.SetUser(lm.User);
FormsAuthentication.SetAuthCookie(model.UserName, false);
if (lm.User.ChangePassword)
{
return Redirect(returnUrl ?? Url.Action("ForceChangePassword", "ChangePassword"));
}
else
{
return Redirect(returnUrl ?? Url.Action("Index", "Details"));
}
}
else
{
if (lm.GroupPermissionMessage.Length > 0)
{
ModelState.AddModelError("", lm.GroupPermissionMessage);
}
else
{
ModelState.AddModelError("", lm.SaveResult.ErrorDescription);
}
return View();
}
}
catch(Exception error)
{
ModelState.AddModelError("", error.Message);
return View();
}
}
else
{
return View();
}
}
The login form has the following definition:
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "LoginForm", returnUrl = Request.QueryString["ReturnUrl"] }))
This is my logic that generates URL for the email:
var queryString = #Url.Action("Index", "Details", new System.Web.Routing.RouteValueDictionary(new { id = model.Ticket.TicketId }), "http", Request.Url.Host);
var ticketUrl = "<a href='"
+ #Url.Action("Index", "Detail", new System.Web.Routing.RouteValueDictionary(new { id = model.Ticket.TicketId, returnUrl = queryString }), "http", Request.Url.Host)
+ "'>Go to your ticket</a>";
I'm expecting Request.QueryString["ReturnURL"] to be equal to the URL I'm opening from the email, so I can redirect to it in my Index method of a controller.
Right now, I'm able to navigate to that URL if I'm already authenticated.
However, if I'm signed off and try to navigate to that URL, Login page is opened first. Then I need to login and should be automatically get redirected to the URL I clicked in my email.
How can I do that?
What am I missing here?
I just learn about ASP.NET MVC. I hava a website with this following scenario:
Login -> Main Page (Index) -> Edit Page (Edit)
So, In LoginController when user login, it will redirect to main page and edit a record from MainPage.
Everytime a new record is created through ASP.NET MVC, the system will send an email to manager. Within email message, there is a hyperlink that will redirect the manager to edit form. But first, he needs to login because the edit form cant be opened unless he login.
Ex:
http://localhost:1212/Main/Edit/ID-001
I have add Authorize Attribute within MainController. But It's only work for Main Page. So I can open Edit Page even I am not login yet.
Here is the MainController:
[Authorize]
public class MainController : Controller
{
string connString = #"Data Source=DESKTOP-FSET3FF,1433; Initial Catalog=INOVA_Data; User Id=sa; Password=Copoe113";
public ActionResult Index(string username)
{
if (Session["username"] != null)
{
string user = Session["username"].ToString();
SqlConnection conn = new SqlConnection(connString);
conn.Open();
string sqlQuery = #"select Animals, Gender from dbo.Animals where Pemilik = #user";
//string h = x => x.
SqlCommand cmd = new SqlCommand(sqlQuery, conn);
cmd.Parameters.AddWithValue("#user", user);
DataTable dt = new DataTable();
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dt);
conn.Close();
return View(dt);
}
else
{
return RedirectToAction("Login", "Login");
}
}
public ActionResult Edit()
{
return View();
}
}
The Second Question, Above I have write my website scenario, that is
Login-> MainPage (Index) -> EditPage (Edit)
Based On Hyperlink On Email, How to make application Redirect to EditPage without redirect to MainPage.
Login -> EditPage (Edit)
EDITED 2nd question
In short, when user's trying to access edit view directly, the application will redirect user to login view. And when heelr login success , the application will redirect user to Edit View.
But now, when login success, the system will redirect the user to main view. How to make the application redirect to edit view after login ?
Important note : (Based on #Tashi Comment, I added this note) If you are use mvc basic application with admin panel than do not worry about the authentication and authorization for whole application with session management.
This is we need when we explicitly use our customization of app and that has to be implement in every controller. Rather than use direct controller for inheritance i.e. MainController : Controller , use custom controller where you check authentication.
/*You have to inherit this basecontroller in every controller*/
public class MainController : BaseController
{
your actionmethods
}
And BaseController like
public class BaseController : Controller
{
public BaseController()
{
if (string.IsNullOrEmpty(SessionManager.SiteUrl))
{
SessionManager.SiteUrl = ConfigurationManager.AppSettings["SiteUrl"].ToString();
}
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (SessionManager.UserId == -1)
{
switch (filterContext.ActionDescriptor.ActionName.ToLower().Trim())
{
case "addeditcontact":
ViewBag.hdnPopupLogout = "0";
return;
default:
filterContext.Result = new RedirectResult(Url.Action("Login", "Home"));
break;
}
}
}
}
Add another property class for session management
public class SessionManager
{
public static int UserId
{
get
{
if (HttpContext.Current.Session["UserId"] != null)
{
return Convert.ToInt32(HttpContext.Current.Session["UserId"]);
}
else return -1;
}
set
{
HttpContext.Current.Session["UserId"] = value;
}
}
public static string UserName
{
get
{
if (HttpContext.Current.Session["UserName"] != null)
{
return Convert.ToString(HttpContext.Current.Session["UserName"]);
}
else return string.Empty;
}
set
{
HttpContext.Current.Session["UserName"] = value;
}
}
//reset user detail and add your custom property
public static void SignOutUser()
{
UserId = -1;
UserName = string.Empty;
}
}
While login set userid in session variable in HomeController like
public ActionResult Login()
{
if (SessionManager.UserId == -1)
{
HttpCookie cookie = Request.Cookies["Login"];// Request.Cookies["Login"];
if (cookie != null)
{
ViewBag.hdnUserID = cookie.Values[0];
ViewBag.hdnPassword = cookie.Values[1];
ViewBag.hdnRemember = "true";
}
return View("Login");
}
else
{
return RedirectToAction("Index", "Home");
}
}
Now this is your architecture ready, I will give your answer
This above things prevent unauthorized access, if there is no user
without authentication.
Now second question is redirect to edit page when hyperlink click. for this while define hyperlink, either create actionmethod to redirect or you can use javascript / ajax method (For authenication) to redirect page.
You need to design html at your end for grid. As this below image.
and above last td of cell in html render as this
<td width="25%" >
<a title="Edit" style="cursor:pointer" onclick="popupContacts(-2147481891,false );">Edit</a>
<a style="cursor:pointer" title="Associate With Client" onclick="popupAssociateClient(-2147481891 );">Associate With Client</a>
<a style="cursor:pointer" title="Update Contacts" onclick="popupUpdateContacts(-2147481891 );">Update Contacts</a> <a style="cursor:pointer" title="Export VCF" onclick="ExportContacttoVcf(-2147481891 );">Export VCF</a>
</td>
for js, redirect to another page, where we first check that user have proper rights else redirect to login in actionmethod.
function popupContactsDetails(clientid, contype) {
window.location.href = URL + "Home/ShowEditContact?Id=" + clientid + ",contype=" + contype;
}
OR You can use the same function as(may be some thing wrong in code as I maniputlate code to publish here, but understand the concept behind this)
function popupContactsDetails(clientid, contype) {
$.ajax({
url: URL + "Home/ShowEditContact?Id=" + clientid + ",contype=" + contype,
type: 'get',
dataType: 'json',
async: false,
// data: ko.toJSON(this),
contentType: 'application/json',
success: function (result) {
if (result = null) {
alert("you are access wrong page);
window.location.href = URL + "Home/login;
} else{
window.location.href = URL + "Home/yourpageurl;
}
}
});
}
On a mobile app, I want to have a Share button that posts an image on behalf of the user, but instead of posting it on this user's timeline, it must be posted on a specific Facebook Page timeline.
By using the Share dialog, it seems there is no way to configure the target of the post (in this case, the Facebook Page). Or at least I couldn't find it.
How would you do it?
Note: the app is made in Unity/C#, although I don't think this would matter much
Try this code :)
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '**appId**',
channelUrl : '//local.facebook-test/channel.html',
status : true,
xfbml : true,
oauth : true
});
FB.login(function(response)
{
if (response.authResponse)
{
var opts = {
message : '**message**',
access_token: '**PageAccessToken**',
name : '**name**',
link : 'https://developers.facebook.com/docs/reference/dialogs/',
description : '**description**',
picture : 'http://url/to/pic.jpg'
};
FB.api('/me/feed', 'post', opts, function(response)
{
if (!response || response.error)
{
console.log(response.error);
alert('Posting error occured');
}else{
alert('Success - Post ID: ' + response.id);
}
});
} else {
alert('Not logged in');
}
}, { scope: 'manage_pages, publish_actions, user_photos' });
};
(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { return; }
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
The idee is to post form data from a normal external Html page to another MVC site controller. Then the data is processed almost like using a webservice.
$(document).ready(function () {
var options = {
target: '#output',
success: function(data){ alert('test success'); },
url: http://localhost:57232/Services/SendFormData,
dataType: json
};
$('form').ajaxForm(options);
});
The ActionResult receives the data correctly in the FormCollection object.
[HttpPost]
public ActionResult SendFormData(FormCollection collection)
{
string s = string.Empty;
return Json(new { Success = true, Message = "Message!" }, JsonRequestBehavior.AllowGet);
}
At this point the success result is returned but when it gets to the external form my browser which is in this case IE tries to save or open the bytes returned instead of calling the success callback function.
Because this page is an external page, and not part of the MVC site I cannot use a View or Partial View. What should the return type be?
You need to return partialview result :
[HttpPost]
public ActionResult Form(Comment feedback)
{
if (feedback != null)
{
feedback.CommentedOn = DateTime.Now;
feedback.CommentId += 1;
if (ModelState.IsValid)
{
BlogPost blogpost = db.BlogPosts.Find(feedback.BlogId);
if (blogpost != null)
blogpost.NoofComments += 1;
db.Entry(blogpost).State = EntityState.Modified;
db.Entry(feedback).State = EntityState.Modified;
db.Comments.Add(feedback);
db.SaveChanges();
return PartialView("CommentSuccess", feedback);
}
}
return PartialView("Comment", feedback);
}
Also in the AjaxForm you need to set the UpdateTargetID:
#using (Ajax.BeginForm("Form", new AjaxOptions() { UpdateTargetId = "FormContainerdiv" , OnSuccess = "$.validator.unobtrusive.parse('form');", OnComplete = "OnComplete();" }))
in the targetId of the Ajax Form you need to mention the div id where you have to display the response data.
<div id="FormContainerdiv">.</div>
#Html.Partial("Comment", item);
</div>