ASP.NET Core Paypal Implementation - asp.net-core

I try to implement a PayPal cart payment in ASP.NET Core. I have a working example in ASP.NET MVC 5 and I try to convert it to ASP.NET Core but I had no success. The point that I can not resolve is how to get the values that I have to get the transactionID, amount paid and Order ID. In ASP.NET MVC 5 the IPN action is as follows:
public ActionResult IPN()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var formVals = new Dictionary<string, string>();
formVals.Add("cmd", "_notify-validate");
string response = GetPayPalResponse(formVals, true);
if (response == "VERIFIED")
{
string transactionID = Request["txn_id"];
string sAmountPaid = Request["mc_gross"];
string orderID = Request["custom"];
:
:
In my ASP.NET Core application the IPN action is executed by PayPal and I have a VERIFIED response but I can not get the next three values. I have tried various ways to get these values without success.
My initial approach was the following:
string transactionID = Request.Query["txn_id"];
string sAmountPaid = Request.Query["mc_gross"];
string orderID = Request.Query["custom"];
Can someone suggest me a way to get these values?

I found a solution to my problem and I will post it just in case someone wants to do something similar.
[Route("PayPal/IPN")]
[HttpPost]
public ActionResult IPN()
{
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
PayPalRespond response = GetPayPalResponse();
if (response.RespondType == RespondTypeEnum.Verified)
{
System.IO.File.AppendAllText(_env.WebRootPath + Path.DirectorySeparatorChar.ToString() + "data.txt", $"{DateTime.Now.ToString()} {response.JsonData}." + Environment.NewLine);
Order order = GetOrder(154);
//check the amount paid
if (order.Total <= response.AmountPaid)
{
// IPN Order successfully transacted. Save changes to database
return Ok();
}
else
{
// Amount Paid is incorrect
}
}
else
{
// Not verified
}
return Content("");
}
PayPalRespond GetPayPalResponse()
{
PayPalRespond output = new PayPalRespond();
var formVals = new Dictionary<string, string>();
formVals.Add("cmd", "_notify-validate");
string paypalUrl = UseSandbox ? "https://www.sandbox.paypal.com/cgi-bin/webscr" : "https://www.paypal.com/cgi-bin/webscr";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(paypalUrl);
// Set values for the request back
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] param;
using (var ms = new MemoryStream(2048))
{
Request.Body.CopyTo(ms);
param = ms.ToArray();
}
string strRequest = Encoding.ASCII.GetString(param);
var QueryValues = System.Web.HttpUtility.ParseQueryString(strRequest);
output.Data = new List<QueryValue>();
foreach (var item in QueryValues.AllKeys)
{
if (item.Equals("txn_id"))
output.TransactionID = QueryValues[item];
else if (item.Equals("mc_gross"))
{
CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
NumberStyles style = NumberStyles.Number;
Decimal amountPaid = 0;
Decimal.TryParse(QueryValues[item], style, culture, out amountPaid);
output.AmountPaid = amountPaid;
}
else if (item.Equals("custom"))
output.OrderID = QueryValues[item];
output.Data.Add(new QueryValue { Name = item, Value = QueryValues[item] });
}
output.JsonData = Newtonsoft.Json.JsonConvert.SerializeObject(output.Data);
StringBuilder sb = new StringBuilder();
sb.Append(strRequest);
foreach (string key in formVals.Keys)
{
sb.AppendFormat("&{0}={1}", key, formVals[key]);
}
strRequest += sb.ToString();
req.ContentLength = strRequest.Length;
//Send the request to PayPal and get the response
string response = "";
using (StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII))
{
streamOut.Write(strRequest);
streamOut.Close();
using (StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()))
{
response = streamIn.ReadToEnd();
}
}
output.RespondType = response.Equals("VERIFIED") ? RespondTypeEnum.Verified : RespondTypeEnum.Invalid;
return output;
}
The enumerator and the classes that you will need are the following:
public enum RespondTypeEnum { Verified, Invalid }
public class PayPalRespond
{
public RespondTypeEnum RespondType { get; set; }
public List<QueryValue> Data { get; set; }
public string JsonData { get; set; }
public string TransactionID { get; set; }
public string OrderID { get; set; }
public Decimal AmountPaid { get; set; }
}
public class QueryValue
{
public string Name { get; set; }
public string Value { get; set; }
}

Related

Display fields (from query) on Swagger UI from complex record

I have a complex record SearchProductsRequest in a GET request that receives the parameters by query (
/v1/products?ids=1,2,3&name=hombre&page=3&pageItems=4&sortField=name&sort=asc ).
app.MapGet(
$"/{ProductCatalogueApi.Version}/products",
(SearchProductsRequest request)
=> ProductApiDelegates.SearchProducts(
request));
In the record, I've implemented the bind async
public static ValueTask<SearchProductsRequest?> BindAsync(HttpContext httpContext, ParameterInfo parameter); and now the parameters from the URL automatically convert the parameters to SearchProductsRequest.
The request is working as intended, but we are using (Swashbuckle -> ) Swagger UI for development.
Swagger UI does not recognize the members from SearchProductsRequest to display them as input boxes. Is there a way to make swagger UI know them and display them so a user consulting the swagger endpoint can pass value through it?
I was hoping to get the following:
Until now, I've only managed to have the fields displayed in swagger if I have all of them in the Map.Get() explicitly.
EDIT:
Adding asked content
Record:
public record SearchProductsRequest
{
public IEnumerable<int>? Ids { get; private set; }
public string? Name { get; private set; }
public PaginationInfoRequest? PaginationInfo { get; private set; }
public SortingInfoRequest? SortingInfo { get; private set; }
public SearchProductsRequest(
IEnumerable<int>? ids,
string? name,
PaginationInfoRequest? PaginationInfo,
SortingInfoRequest? SortingInfo)
{
this.Ids = ids;
this.Name = name;
this.PaginationInfo = PaginationInfo;
this.SortingInfo = SortingInfo;
}
public static ValueTask<SearchProductsRequest?> BindAsync(
HttpContext httpContext,
ParameterInfo parameter)
{
var ids = ParseIds(httpContext);
var name = httpContext?.Request.Query["name"] ?? string.Empty;
PaginationInfoRequest? pagination = null;
SortingInfoRequest? sorting = null;
if (int.TryParse(httpContext?.Request.Query["page"], out var page)
&& int.TryParse(httpContext?.Request.Query["pageItems"], out var pageItems))
{
pagination = new PaginationInfoRequest(page, pageItems);
}
var sortField = httpContext?.Request.Query["sortField"].ToString();
if (!string.IsNullOrEmpty(sortField))
{
sorting = new SortingInfoRequest(
sortField,
httpContext?.Request.Query["sort"].ToString() == "asc");
}
return ValueTask.FromResult<SearchProductsRequest?>(
new SearchProductsRequest(
ids,
name!,
pagination,
sorting));
}
#pragma warning disable SA1011 // Closing square brackets should be spaced correctly
private static int[]? ParseIds(HttpContext httpContext)
{
int[]? ids = null;
var commaSeparatedIds = httpContext?.Request.Query["ids"]
.ToString();
if (!string.IsNullOrEmpty(commaSeparatedIds))
{
ids = commaSeparatedIds
.Split(",")
.Select(int.Parse)
.ToArray() ?? Array.Empty<int>();
}
return ids;
}
#pragma warning restore SA1011 // Closing square brackets should be spaced correctly
}
Delegate:
internal static async Task<IResult> SearchProducts(
ILogger<ProductApiDelegates> logger,
IMapper mapper,
SearchProductsRequest request,
IValidator<SearchProductsRequest> validator,
IProductService productService)
{
using var activity = s_activitySource.StartActivity("Search products");
var validationResult = await validator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.GetErrors();
logger.LogError("Bad Request: {Errors}", errors);
return Results.BadRequest();
}
try
{
logger.LogInformation("Searching product details by name");
var filtersContainer = mapper.Map<SearchProductsFiltersContainer>(request);
var products = await productService.SearchProductsAsync(filtersContainer);
if (products == null)
{
return Results.NotFound();
}
var searchProducts = BuildSearchProducts(mapper, products);
var paginationInfo = await BuildPaginationInfo(filtersContainer, productService);
var response = new SearchProductsResponse(searchProducts, paginationInfo);
return Results.Ok(response);
}
catch (Exception ex)
{
logger.LogError(ex, "Error searching the products");
return Results.Problem();
}
}

XML Serialization - Required attribute

I have a class that should represent a controller's action parameter and I'd like its properties to be "required" (meaning, you get a status code 400 or something in case it's passed as null). I managed to get it done using System.ComponentModel.DataAnnotations, but the ErrorMessage that I pass to the constructor of the Required attribute is never shown.
[XmlRoot(ElementName = "root")]
public class Request
{
[XmlElement(ElementName = "prop")]
[Required(ErrorMessage = "The property is required.")]
public string Property { get; set; }
[XmlElement(ElementName = "another")]
[Required(ErrorMessage = "The property is required.")]
public string Another { get; set; }
}
Action:
[HttpPost]
public IActionResult Post([FromBody] Request value)
{
return Ok(value); //ignore this, it's just for testing purposes...
}
However, if I don't pass the Property value, I get a 400 that doesn't contain the ErrorMessage I passed earlier. Am I missing something here?
<ValidationProblemDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>One or more validation errors occurred.</Title>
<Status>400</Status>
</ValidationProblemDetails>
My Startup has Xml formatters added to it:
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true;
options.InputFormatters.Insert(0, new XmlSerializerInputFormatter(options));
options.OutputFormatters.Insert(0, new XmlSerializerOutputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
The body of the request looks like this, and it doesn't have "Property":
<root>
<another>Test</another>
<!-- Property "Property" is missing here -->
</root>
Kudos to Code Rethinked for the huge help - Customizing automatic HTTP 400 error response in ASP.NET Core Web APIs.
An approach that I managed to figure out eventually includes the use of services.Configure in my Startup.ConfigureServices method.
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
return new OkObjectResult(new CustomResponse(someStatusCode, context))
{
ContentTypes = { "application/xml" }
};
};
});
So, I made a class named CustomResponse that holds the status code I want to retrieve and all the validation errors (including the ones where my Required property was not passed to the API).
[XmlRoot(ElementName = "rcemsTrxSubReqAck")]
public class CustomResponse
{
[XmlElement(ElementName = "Status")]
public string Status { get; set; }
[XmlArray(ElementName = "Errors"), XmlArrayItem(ElementName = "Error")]
public string[] Errors { get; set; }
public CustomResponse(int status, ActionContext context)
{
Status = status;
Errors = ConstructErrorMessages(context);
}
private string[] ConstructErrorMessages(ActionContext context)
{
if (context == null)
{
return null;
}
string[] arr = new string[context.ModelState.ErrorCount];
int i = 0;
foreach (var keyModelStatePair in context.ModelState)
{
var key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
if (errors != null && errors.Count > 0)
{
if (errors.Count == 1)
{
var errorMessage = GetErrorMessage(errors[0]);
arr[i] = $"{key}: {errorMessage}";
}
else
{
var errorMessages = new string[errors.Count];
for (var j = 0; j < errors.Count; j++)
{
errorMessages[j] = GetErrorMessage(errors[j]);
}
arr[i] = $"{key}: {errorMessages.ToString()}";
}
i++;
}
}
return arr;
}
private string GetErrorMessage(ModelError error)
{
return string.IsNullOrEmpty(error.ErrorMessage) ? "The input was not valid." : error.ErrorMessage;
}
}

Setting Object values from SQL Query

I am trying to iterate through an SQL query and set the objects parameters to particular values. The only value that seems to be functioning correctly is the workpack.JobCardIDs, as I can implement a foreach loop to display the results. If I try to set a Label's Text property to a workpack.WorkPackTitle for example, it will display a blank even though the database value is something for every line.
I am fairly new to the OOP so not entirely sure if there is something I am missing that's fundamental.
public class WorkPack
{
public int ID { get; set; }
public string WorkPackNumber { get; set; }
public string WorkPackTitle { get; set; }
public string WorkPackDescription { get; set; }
public Boolean IFC { get; set; }
public string SPA { get; set; }
public string Writer { get; set; }
public string Organization { get; set; }
public List<int> JobCardIDs { get; set; }
public int JobCard { get; set; }
}
public static WorkPack PopulateWorkPackObject(WorkPack workpack, int workPackID)
{
string ConnectionString = ConfigurationManager.ConnectionStrings["vmdatamanagerConnectionString"].ConnectionString;
string sqlCall = "I HAVE REMOVED CALL BUT VERIFIED IT FUNCTIONS (SELECT columns FROM workpackdatabase where workpackname = x";
using (SqlConnection con = new SqlConnection(ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(sqlCall, con))
{
cmd.Connection.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (reader.IsDBNull(reader.GetOrdinal("PARAM1")) == false)
workpack.WorkPackNumber = (reader.GetString(reader.GetOrdinal("PARAM1")));
if (reader.IsDBNull(reader.GetOrdinal("PARAM2")) == false)
workpack.WorkPackTitle = reader.GetString(reader.GetOrdinal("PARAM2"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM3")) == false)
workpack.WorkPackDescription = reader.GetString(reader.GetOrdinal("PARAM3"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM4")) == false)
workpack.IFC = reader.GetBoolean(reader.GetOrdinal("PARAM4"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM5")) == false)
workpack.SPA = reader.GetString(reader.GetOrdinal("PARAM5"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM6")) == false)
workpack.Writer = reader.GetString(reader.GetOrdinal("PARAM6"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM7")) == false)
workpack.Organization = reader.GetString(reader.GetOrdinal("PARAM7"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM8")) == false)
jobCardIDs.Add(reader.GetInt32(reader.GetOrdinal("PARAM8")));
}
workpack.JobCardIDs = jobCardIDs;
return workpack;
}
}
}
}
Looks like you never create a local instance of your jobCardIDs List. You'll want to do this just inside your ExecuteReader block. See below. GL
public static WorkPack PopulateWorkPackObject(WorkPack workpack, int workPackID)
{
string ConnectionString = ConfigurationManager.ConnectionStrings["vmdatamanagerConnectionString"].ConnectionString;
string sqlCall = "I HAVE REMOVED CALL BUT VERIFIED IT FUNCTIONS (SELECT columns FROM workpackdatabase where workpackname = x";
using (SqlConnection con = new SqlConnection(ConnectionString))
{
using (SqlCommand cmd = new SqlCommand(sqlCall, con))
{
cmd.Connection.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
List<int> jobCardIDs = new List<int>(); //<--***THIS IS THE LINE YOU NEED TO ADD***
while (reader.Read())
{
if (reader.IsDBNull(reader.GetOrdinal("PARAM1")) == false)
workpack.WorkPackNumber = (reader.GetString(reader.GetOrdinal("PARAM1")));
if (reader.IsDBNull(reader.GetOrdinal("PARAM2")) == false)
workpack.WorkPackTitle = reader.GetString(reader.GetOrdinal("PARAM2"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM3")) == false)
workpack.WorkPackDescription = reader.GetString(reader.GetOrdinal("PARAM3"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM4")) == false)
workpack.IFC = reader.GetBoolean(reader.GetOrdinal("PARAM4"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM5")) == false)
workpack.SPA = reader.GetString(reader.GetOrdinal("PARAM5"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM6")) == false)
workpack.Writer = reader.GetString(reader.GetOrdinal("PARAM6"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM7")) == false)
workpack.Organization = reader.GetString(reader.GetOrdinal("PARAM7"));
if (reader.IsDBNull(reader.GetOrdinal("PARAM8")) == false)
jobCardIDs.Add(reader.GetInt32(reader.GetOrdinal("PARAM8")));
}
workpack.JobCardIDs = jobCardIDs;
return workpack;
}
}
}
}
The previous answers given by the community did not fix the issue, although I did put that extra snippet it.
The issue was when the objects were being created and passed between post backs. The object would be relevant on selection of the job card tab but once the page loaded there was no code to rebuild that instance.
Adding
Object foo = new Object();
in the page_Load() and rebuilding fixed the issue. If anyone has any suggestions on how to keep an instance alive I am all for hearing it. I think ViewState() and also Session[] were applicable methods for doing so.

Getting token with Quickblox in C#

I'm trying to access my QuickBlox app and getting a token using the REST API.
My code looks like this:
http://pastebin.com/rp2KLMp2
The request looks like this (sensitive info removed):
application_id=xxxx&auth_key=xxxxxxxxxx&nonce=2851&timestamp=1389951758&signature=D481F13E87F47D4C17697EF9D2C8E25777E09079
I'm getting the error:
The remote server returned an error: (422) Unprocessable Entity
What could be the problem?
public string Timestamp()
{
long ticks = DateTime.UtcNow.Ticks -
DateTime.Parse("01/01/1970 00:00:00").Ticks;
ticks /= 10000000;
return ticks.ToString();
}
public string Hash(string input, string key)
{
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
HMACSHA1 myhmacsha1 = new HMACSHA1(keyByte);
byte[] byteArray = Encoding.ASCII.GetBytes(input);
MemoryStream stream = new MemoryStream(byteArray);
byte[] hashValue = myhmacsha1.ComputeHash(stream);
return string.Join("", Array.ConvertAll(hashValue, b => b.ToString("x2")));
}
public string GetToken()
{
if (HttpContext.Current == null || String.IsNullOrEmpty(Convert.ToString(HttpContext.Current.Cache["QuickBloxToken"])))
{
string url = "https://api.quickblox.com"; //ConfigurationManager.AppSettings["ChatUrl"].ToString();
HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(url + "/session.xml");
httpWReq.UserAgent = ".NET Framework Test Client";
string application_id = System.Configuration.ConfigurationManager.AppSettings["QuickApplication_id"].ToString();
string auth_key = System.Configuration.ConfigurationManager.AppSettings["QuickAuth_key"].ToString();
string timestamp = Timestamp();
string auth_secret = System.Configuration.ConfigurationManager.AppSettings["QuickAuth_secret"].ToString();
ASCIIEncoding encoding = new ASCIIEncoding();
string postData = "application_id=" + application_id;
postData += "&auth_key=" + auth_key;
Random rand = new Random();
postData += "&nonce=" + rand.Next(1000, 9999);
postData += "&timestamp=" + timestamp;
string signature = Hash(postData, auth_secret);
postData += "&signature=" + signature;
byte[] data = encoding.GetBytes(postData);
httpWReq.Method = "POST";
httpWReq.ContentLength = data.Length;
httpWReq.Headers["QuickBlox-REST-API-Version"] = "0.1.0";
using (Stream stream = httpWReq.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
HttpWebResponse response = (HttpWebResponse)httpWReq.GetResponse();
string responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(responseString);
var nodes = xmlDoc.SelectNodes("session");
string token = nodes[0].SelectSingleNode("token").InnerText;
if (HttpContext.Current != null)
HttpContext.Current.Cache.Add("QuickBloxToken", token, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 70, 0), System.Web.Caching.CacheItemPriority.Default, null);
return token;
}
else
return Convert.ToString(HttpContext.Current.Cache["QuickBloxToken"]);
}
Yes, when you retrieve this http-status in server response you will retrieve also response.body with error message which describes a reason this response. Could you check him?
//Use Restshrap
public static string token()
{
Applicationdetail obj = new Applicationdetail();
string application_id = obj.application_id.ToString();
string auth_key = obj.auth_key;
string timestamp = Timestamp();
string auth_secret = obj.secretkey;
string postData = "application_id=" + application_id;
postData += "&auth_key=" + auth_key;
Random rand = new Random();
postData += "&nonce=" + rand.Next(1000, 9999);
postData += "&timestamp=" + timestamp;
string signature = Hash(postData, auth_secret);
postData += "&signature=" + signature;
RestSharp.RestClient client = new RestSharp.RestClient("https://api.quickblox.com/session.json?"+ postData);
RestSharp.RestRequest request = new RestSharp.RestRequest(RestSharp.Method.POST);
request.AddHeader("QuickBlox-REST-API-Version", " 0.1.0");
var result = client.Execute(request);
if (result != null && !string.IsNullOrEmpty(result.Content))
{
sessionRootObject tokenobj =
JsonConvert.DeserializeObject<sessionRootObject>
(result.Content);
return tokenobj.session.token;
}
else
{
return "";
}
}
// add class
public class Session
{
public int application_id { get; set; }
public DateTime created_at { get; set; }
public int id { get; set; }
public int nonce { get; set; }
public string token { get; set; }
public int ts { get; set; }
public DateTime updated_at { get; set; }
public int user_id { get; set; }
public string _id { get; set; }
}
public class sessionRootObject
{
public Session session { get; set; }
}
public class Applicationdetail
{
public int application_id { get {
return add appid;
} }
public string auth_key { get { return "enter auth key"; } }
public string secretkey { get { return "enter secretkey"; } }
}

Getting email from oauth authentication (Microsoft)

How can I get the email from microsoft account? I'm doing the following:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.ExtraData["????"];
}
}
For google and facebook I'm able to get the email but I can't with microsoft? What kew should I use?
Solution:
public class MicrosoftScopedClient : IAuthenticationClient
{
private string clientId;
private string clientSecret;
private string scope;
private const string baseUrl = "https://login.live.com/oauth20_authorize.srf";
private const string tokenUrl = "https://login.live.com/oauth20_token.srf";
public MicrosoftScopedClient(string clientId, string clientSecret, string scope)
{
this.clientId = clientId;
this.clientSecret = clientSecret;
this.scope = scope;
}
public string ProviderName
{
get { return "Microsoft"; }
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.ToString();
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["email"];
userData.Remove("id");
userData.Remove("email");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = QueryAccessToken(redirectURI, accessCode);
if (token == null || token == "")
{
return null;
}
var userData = GetUserData(token);
return userData;
}
private IDictionary<string, string> GetUserData(string accessToken)
{
ExtendedMicrosoftClientUserData graph;
var request =
WebRequest.Create(
"https://apis.live.net/v5.0/me?access_token=" + EscapeUriDataStringRfc3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
graph = JsonConvert.DeserializeObject<ExtendedMicrosoftClientUserData>(data);
}
}
}
var userData = new Dictionary<string, string>();
userData.Add("id", graph.Id);
userData.Add("username", graph.Name);
userData.Add("name", graph.Name);
userData.Add("link", graph.Link == null ? null : graph.Link.AbsoluteUri);
userData.Add("gender", graph.Gender);
userData.Add("firstname", graph.FirstName);
userData.Add("lastname", graph.LastName);
userData.Add("email", graph.Emails.Preferred);
return userData;
}
private string QueryAccessToken(string returnUrl, string authorizationCode)
{
var entity =
CreateQueryString(
new Dictionary<string, string> {
{ "client_id", this.clientId },
{ "redirect_uri", returnUrl },
{ "client_secret", this.clientSecret},
{ "code", authorizationCode },
{ "grant_type", "authorization_code" },
});
WebRequest tokenRequest = WebRequest.Create(tokenUrl);
tokenRequest.ContentType = "application/x-www-form-urlencoded";
tokenRequest.ContentLength = entity.Length;
tokenRequest.Method = "POST";
using (Stream requestStream = tokenRequest.GetRequestStream())
{
var writer = new StreamWriter(requestStream);
writer.Write(entity);
writer.Flush();
}
HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
if (tokenResponse.StatusCode == HttpStatusCode.OK)
{
using (Stream responseStream = tokenResponse.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
string data = sr.ReadToEnd();
var tokenData = JsonConvert.DeserializeObject<OAuth2AccessTokenData>(data);
if (tokenData != null)
{
return tokenData.AccessToken;
}
}
}
}
return null;
}
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
private static string EscapeUriDataStringRfc3986(string value)
{
StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
private static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args)
{
if (!args.Any())
{
return string.Empty;
}
StringBuilder sb = new StringBuilder(args.Count() * 10);
foreach (var p in args)
{
sb.Append(EscapeUriDataStringRfc3986(p.Key));
sb.Append('=');
sb.Append(EscapeUriDataStringRfc3986(p.Value));
sb.Append('&');
}
sb.Length--; // remove trailing &
return sb.ToString();
}
protected class ExtendedMicrosoftClientUserData
{
public string FirstName { get; set; }
public string Gender { get; set; }
public string Id { get; set; }
public string LastName { get; set; }
public Uri Link { get; set; }
public string Name { get; set; }
public Emails Emails { get; set; }
}
protected class Emails
{
public string Preferred { get; set; }
public string Account { get; set; }
public string Personal { get; set; }
public string Business { get; set; }
}
}
AuthConfig.cs
public static class AuthConfig
{
public static void RegisterAuth()
{
Dictionary<string, object> MicrosoftsocialData = new Dictionary<string, object>();
MicrosoftsocialData.Add("Icon", "../Content/icons/microsoft.png");
OAuthWebSecurity.RegisterClient(new MicrosoftScopedClient("XXXXXXXX", "YYYYYYYYYYYYY",
"wl.basic wl.emails"), "Microsoft", MicrosoftsocialData);
//......
}
}
Usage:
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
//...
string email = null;
if (result.Provider.ToLower() == "google")
{
email = result.ExtraData["email"];
}
else if (result.Provider.ToLower() == "facebook")
{
email = result.ExtraData["username"];
}
else if (result.Provider.ToLower() == "microsoft")
{
email = result.UserName;
}
}
Based on: How OAuthWebSecurity to obtain emails for different oauth clients, but Microsoft Client doesn’t return email, it didn’t include scope “wl.emails”
or even simpler: https://stackoverflow.com/a/22723713/1586498
var mo =
new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationOptions
{
CallbackPath = new Microsoft.Owin.PathString("/Callbacks/External"),//register at oAuth provider
ClientId = "<<yourclientid>>",
ClientSecret = "<<yourclientsecret>>",
Provider = new Microsoft.Owin.Security.MicrosoftAccount.MicrosoftAccountAuthenticationProvider
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim(providerKey, context.Identity.AuthenticationType));
context.Identity.AddClaim(new Claim(ClaimTypes.Name, context.Identity.FindFirstValue(ClaimTypes.Name)));
return System.Threading.Tasks.Task.FromResult(0);
}
}
};
mo.Scope.Add("wl.basic");
mo.Scope.Add("wl.emails"); //HERE IS THE GOLD
app.UseMicrosoftAccountAuthentication(mo);
and my way of grabbing them:
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
externalIdentity.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Email));
amp's answer really helped me out.
Also want to mention that you have to check the 'Live SDK support' checkbox when you register your application (https://apps.dev.microsoft.com/) - otherwise the OAuth service complains that you don't have a client secret (even if you do).
Just wanted to add how to do this without using the AuthConfig.cs stuff in case anyone is interested (a bit more manual, but it makes it easier to understand if you're not familiar with the framework):
public ActionResult LoginWithMicrosoftAccount(CancellationToken cancellationToken)
{
var client = new MicrosoftScopedClient(appID, appsecret, "wl.basic wl.emails");
var urlNoQueryString = Request.Url.GetLeftPart(UriPartial.Path);
AuthenticationResult result = null;
if(Request.QueryString["error"]!= null)
{//Microsoft service returns error
return View();
}
if (Request.QueryString["code"] != null)
{
result = client.VerifyAuthentication(this.HttpContext);
//at this point, you should get the username from result.UserName
}
if(Request.QueryString["code"]==null || result.UserName == null)
{//will do the redirection
client.RequestAuthentication(this.HttpContext, new Uri(urlNoQueryString));
}
return View();
}