What extra values does Stack Exchange OpenID support? - c#

I am trying to get the username / display name, but I have no idea what are the supported values. I got the email and realname, but I don't know what returns the username / display name.
Is there a documentation or something about this?
My current code:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
// returned value: null
//extraData.Add("username", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
// returned value: null
//fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.Alias);
request.AddExtension(fetchRequest);
}
}

What's your arrow is pointing to their isn't a display name (StackID has no notion of display names, your login is your email address) but an optional "Vanity Id".
For example:
Gives me the vanity OpenID of https://openid.stackexchange.com/kevin.montrose . This is just an easier to remember alias for relying parties that require manual entry of OpenID urls.
Email and Real Name/Full Name are the only attributes StackID supports querying for, and will return both via either SREG or AX extensions (as seen in the code).

Related

How to get properties to return in the body of an Authenticate request?

I'm setting up a new ASP.NET API project, copying the basics from an existing project. For authentication, I've set up a class that inherits from OpenIdConnectServerProvider. The authentication is working fine in HandleTokenRequest(). But in addition to the access_token, I want to return some additional data for the client on a successful login. Copying from the existing project, I did the following in HandleTokenRequest():
var data = new Dictionary<string, string>
{
{"userId", user.UserId.ToString()},
{"userTypeId", ((int) user.Type).ToString(CultureInfo.InvariantCulture)}
};
var properties = new AuthenticationProperties(data);
var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, context.Scheme.Name);
context.Validate(ticket);
For some reason, however, I'm not getting back the userId and userTypeId in the response body. Here's what I get in the response:
{
"token_type":"Bearer",
"access_token":"CfDJ...etc",
"expires_in":1209600
}
There's obviously some secret switch I'm missing that will inject the AuthenticationProperties into the response body. What is it?
You need to override ApplyTokenResponse in the OpenIdConnectServerProvider to explicitly add the properties. I think they’re not returned by default (they’re just present for reference, as needed).
public override Task ApplyTokenResponse(ApplyTokenResponseContext context)
{
if (context.Ticket != null)
{
foreach (var property in context.Ticket.Properties.Items)
{
context.Response.AddParameter(property.Key, property.Value);
}
}
return Task.CompletedTask;
}

Why do I always get the same IP when getting Request.UserHostAddress in WebAPI custom action filter?

I've created a Web API custom action filter to log incoming calls. I'm trying to get the caller's IP address and everything I've found says to use Request.UserHostAddress. The problem is that no matter where the call is coming from, the IP is the same.
Here's the code for my action filter:
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var name = actionContext.ActionDescriptor.ActionName;
// Get the sender address
var caller = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
// Log the call
SystemBL.InsertSiteLog("WebAPI:" + name, "From:" + caller);
}
}
I've also tried with:
var caller = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.ServerVariables["REMOTE_ADDR"].ToString();
but the result was the same. Any ideas?
Found the answer here: HttpContext.Current.Request.UserHostAddress is null.
Basically, I needed to sort out the forwarding. The final code is:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var name = actionContext.ActionDescriptor.ActionName;
// Get the sender address
var myRequest = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request;
var ip = myRequest.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (!string.IsNullOrEmpty(ip))
{
string[] ipRange = ip.Split(',');
int le = ipRange.Length - 1;
string trueIP = ipRange[le];
}
else
{
ip = myRequest.ServerVariables["REMOTE_ADDR"];
}
// Log the call
SystemBL.InsertSiteLog("WebAPI:" + name, "From:" + ip);
}
Thanks everyone. I'll mark it as the answer in 2 days when it lets me.

Decoding Json data from API

I am new to Json and have to deserialize some Json data received from a url.
I am using an API provided for me to log a user into an application.
The API:
Logging in
To login, make the following call
<site>/Users/login/username:<email>/password:<password>.json
• Success Response
{
“response”:{
“sessionId” : “<sessionId>”,
“businesses” : [{
“<bizId-1>” : “<bizName-1>”,
“<bizId-2>” : “<bizName-2>” ,
“<bizId-n>” : “<bizName-n>”
}]
},
“messages” :{"msgs":"","errs":""}
}
An example of an actual response is as below.
{
"response":{
"sessionId":"50b702d8-78dc-4d65-9de8-2510c327a7be",
"businesses":[
{"50c657af0ad8-4ce7-bb08-1d60c327a7be":"All of The Lights"},
{"50cf705a-ded4-4c7d-95df-51c8c327a7be":"Tomatoes Inc"},
{"50d2cf88-e664-4103-99f9-1aa0c327a7be":"Joe's Jalepinos"},
{"50d2d9b9-c358-4129-b9ec-1aa0c327a7be":"Pizza Place"},
{"50eed93e-f49c-4dff-8c7a-33f0c327a7be":"Samsung"},
{"51036f10-e9c0-47ecb73d-0f50c327a7be":"Peppers"},
{"51036fcd-a6b8-4103-8e160f82c327a7be":"Puppy"}
]
},
"messages":{"msgs":"","errs":""}
}
I have the following code:
try
{
serverUrl = "https://eko-app.com/Users/login/username:" + usernameEntered + "/password:" + passwordEntered + ".json";
var w = new WebClient();
var jsonData = string.Empty;
// make the login api call
jsonData = w.DownloadString(serverUrl);
if (!string.IsNullOrEmpty(jsonData))
{
var dataResult = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonData);
// need help here
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
I first want to retrieve the session Id from the response. If it exists, then a user is allowed to login, if the session id is null then login is rejected. How do I do this?
Also, once a user is logged in, I would like to create a list showing the number of businesses per user and their respective business Ids.
Any help appreciated.
To do it in a strongly typed way create a class that mirrors the Json you want to deserialise (only the properties you are interested in are necessary).
class LoginResponseDetails
{
public string SessionId {get;set;} // this might be better as a GUID
public Hashtable Businesses {get;set;} // this could be Dictionary<string, string> or Dictionary<Guid, string>
// other properties here...
}
class LoginResponse
{
LoginResponseDetails Response {get;set;}
// other properties here...
}
Then deserialise to this class:
var response = Newtonsoft.Json.JsonConvert.DeserializeObject<LoginResponse>(jsonData);
var sessionId = response.Response.SessionId;
etc...
You are almost there, simply need to look at the properties of the anonymous type.
Try something like
if (!string.IsNullOrEmpty(jsonData))
{
var dataResult = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonData);
if (dataResult != null && dataResult.response.sessionId != null)
{
// logged in
// iterate and show bussiness list
} else {
Console.WriteLine("Login failed");
}
}
See the JSON.NET docs - http://james.newtonking.com/json/help/html/DeserializeAnonymousType.htm

Update on any properties with odata

Do you know how I could update an entity in WCF Data Services with OData syntax without the key property of the entity.
For example, an entity:
public class Product
{
[Key]
public int Id { get; set; }
public string Reference { get; set; }
}
I would like to make this request:
PUT myservice.svc/Product('REFXX')
with 'REFXXX' corresponding do the Reference property (which is unique).
Any idea?
Currently there is no way to do this - the issue is if you pass the following request to the server (PUT myservice.svc/Product('REFXX')), how will the server know that REFXX is the value for the unique property and not the key property.
If you really want to update the client based on the unique property, make sure the server exposes that unique property as key.
Thanks
Pratik
I wrote a IDispatchMessageInspector, parse the url and replace the match element in the request parameter with a correct syntax and the real key. I know that the key is not the real "Key" with a specific user agent or with the syntax Service.svc/Entity(SecondaryKey=value), which is used normally for multiple pk's.
so in the method AfterReceiveRequest the process is:
parse the url Service.svc/Entity(SecondaryKey=value)
get the key value of the entity (by building a dynamic linq expression)
change the match element of the request with Service.svc/Entity(PKValue)
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (request.Properties.ContainsKey("UriTemplateMatchResults") && HttpContext.Current != null)
{
//get match for current request
UriTemplateMatch match = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];
Utils.ODataBasicUriParser uriParser = new Utils.ODataBasicUriParser(match.RequestUri.PathAndQuery);
//verify if this is a SecondaryKey request
if (uriParser.IsEntityQuery && uriParser.IsSecondaryKeyQuery)
{
//TODO this syntax is also used for entities with multiple pk's, test it
//get a new data context
//TODO see if this can be improved, avoid two datacontext for one request
DataContext ctx = new DataContext();
Type outType;
//get entity type name from the service name
string entityName = DataContext.GetEntityNameByServiceName(uriParser.EntityServiceName);
//get the pk for the entity
string id = ctx.GetEntityId(entityName, uriParser.EntityKey, uriParser.EntityId, out outType);
//verify if the pk has been found or cancel this to continue with standart request process
if (string.IsNullOrEmpty(id))
{
Trace.TraceWarning(string.Format("Key property not found for the the entity:{0}, with secondaryKeyName:{1} and secondaryKeyValue:{2}",
entityName, uriParser.EntityKey, uriParser.EntityId));
return System.Net.HttpStatusCode.NotFound;
}
//in odata syntax quotes are required for string values, nothing for numbers
string quote = outType.FullName == typeof(Int32).FullName || outType.FullName == typeof(Int64).FullName ? string.Empty : "'";
//build the new standart resource uri with the primary key
var newUri = new Uri(string.Format("{0}/{1}({2}{3}{2})", match.BaseUri.ToString(), uriParser.EntityServiceName, quote, id));
//create a new match to replace in the current request, with the new Uri
UriTemplateMatch newMatch = NewMatch(match, newUri);
//set request values
request.Properties["UriTemplateMatchResults"] = newMatch;
request.Headers.To = newUri;
request.Properties.Via = newUri;
}
}
return null;
}
UriTemplateMatch NewMatch(UriTemplateMatch match, Uri newUri)
{
UriTemplateMatch newMatch = new UriTemplateMatch();
newMatch.RequestUri = newUri;
newMatch.Data = match.Data;
newMatch.BaseUri = match.BaseUri;
return newMatch;
}
works for my current needs

Storing more information using FormsAuthentication.SetAuthCookie

I am using aspx and c# for a setting a authentication cookie for a login.
FormsAuthentication.SetAuthCookie(UserName, True)
I want to store more information in the same cookie. Can I add values to this authentication cookie or do I have to use a second http cookie?
Basically I'm looking for away to store the User's Id so I may be able to access the database using the users table row key
Thanks,
Eden
You can add user data to the FormsAuthenticationTicket, then generate the cookie yourself.
There's an example in the the MSDN documentation for FormsAuthenticationTicket.
EDIT
Note that when creating the ticket, you need to set the timeout, which in general you will want to be the same as the value configured in web.config. Unfortunately, in the Framework 3.5 or earlier, the FormsAuthentication class does not expose this timeout publicly. For a workaround, use one of the techniques described in the response to this connect feedback item.
UPDATE
That Connect feedback item is no longer there, sadly. Wish you had briefly described what the techniques were.
Yes, it's a pity Microsoft has discarded historical Connect items. IIRC, the two techniques they suggested were:
Use WebConfigurationManager to read the relevant configuration section and get the timeout value.
Create a cookie using FormsAuthentication.GetAuthCookie, decrypt it using FormsAuthentication.Decrypt and inspect the generated FormsAuthenticationTicket.
Or upgrade to .NET 4.x where there is a FormsAuthentication.Timeout property.
See this question for more info
You can put whatever you want in the auth cookie as long as it's useful to you. That said, if you're putting sensitive information you should, at the very least, encrypt it, but I'd recommend against putting sensitive information there. You can do something like:
Forms.SetAuthCookie (UserName + "|" + UserId, true);
Then, whenever you need the username or the user id, it is there. Just load the cookie and parse out the values you need.
Again, I'd advise against doing this, especially as I have it presented above. That said, it is possible. You should create accessor methods to pull the data back out:
public int CurrentUserId
{
get
{
int userId = 0;
if (HttpContext.Current.Request.IsAuthenticated)
{
userId = Convert.ToInt32(HttpContext.Current.User.Identity.Name.Split('|')[1]);
}
return userId;
}
}
public string CurrentUserName
{
get
{
string userName = string.Empty;
if (HttpContext.Current.Request.IsAuthenticated)
{
userName = HttpContext.Current.User.Identity.Name.Split('|')[0];
}
return userName;
}
}
Yes it is smart to use "|" to put more info. If Microsoft have another overloaded method
public static void SetAuthCookie(String userName, bool createPersistentCookie, string userData)`
Then our life will be much easier, our code will be safer.
Pass that user ID as the userName param.
FormsAuthentication.SetAuthCookie(userId, True)
How are you securing your auth tickets?
You can store additional information in the UserData property of the FormsAuthenticationTicket:
using Newtonsoft.Json;
using System.Web;
using System.Web.Security;
public class LoggedInUser
{
public string FirstName { get; set; } = null;
public bool IsAdmin { get; set; } = false;
}
public static class Authentication
{
static void SignIn(
HttpContextBase context,
string emailAddress,
bool rememberMe,
LoggedInUser user = null)
{
var cookie = FormsAuthentication.GetAuthCookie(
emailAddress.ToLower(),
rememberMe);
var oldTicket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(
oldTicket.Version,
oldTicket.Name,
oldTicket.IssueDate,
oldTicket.Expiration,
oldTicket.IsPersistent,
JsonConvert.SerializeObject(user ?? new LoggedInUser()));
cookie.Value = FormsAuthentication.Encrypt(newTicket);
context.Response.Cookies.Add(cookie);
}
static void SignOut(HttpContextBase context)
{
FormsAuthentication.SignOut();
}
static LoggedInUser GetLoggedInUser()
{
if (HttpContext.Current.User?.Identity?.Name != null && HttpContext.Current.User?.Identity is FormsIdentity identity)
return JsonConvert.DeserializeObject<LoggedInUser>(identity.Ticket.UserData);
return new LoggedInUser();
}
}
Further Reading: https://learn.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/introduction/forms-authentication-configuration-and-advanced-topics-cs#step-4-storing-additional-user-data-in-the-ticket

Categories

Resources