OAuth is an option to work alongside Forms Authentication to enable users to log in with credentials from another site such as Facebook or Twitter.
Developers can extend OAuth support to other OAuth services or even add further functionality to the existing OAuth clients. The providers that come with Telligent Community collect only the information needed from a user to log them into the site. They do not support posting content back to Facebook or Twitter. However, a custom provider could be created to extend the default functionality.
[toc]
Overview of Existing OAuth Clients
Community has a number of OAuth clients that can be managed simply by enabling and configuring the corresponding plugin. These are all implementations of the IOAuthClient interface.
- Facebook authentication
- Google authentication
- LinkedIn authentication
- Live Connect authentication
- Salesforce authentication
- Twitter authentication
Creating an OAuth Client
Our sample will be based on Pinterest Oauth process. Usage of this example requires you to create an app in Pinterest's Dev Center so you can obtain a client key and secret. You can find more details and set up a client app here: https://developers.pinterest.com/docs/api/overview.
Required DLLs:
- Telligent.Evolution.Components
- Telligent.Evolution.Controls
Defining Client Properties
We define several properties statically from the interface for later use. The urls used should be specified by the third party service you are authenticating against.
public string ClientName { get { return "Pinterest"; } } public string ClientType { get { return "pinterest"; } } public virtual string AuthorizeBaseUrl { get { return "https://api.pinterest.com/oauth"; } } public virtual string AccessTokenUrl { get { return "https://api.pinterest.com/v1/oauth/token"; } } public string ThemeColor { get { return "BD081C"; } } public virtual string ConsumerKey { get { return Configuration.GetString("ConsumerKey"); } } public virtual string ConsumerSecret { get { return Configuration.GetString("ConsumerSecret"); } } // Your privacy statement should include what privacy information // is being collected about the user using this OAuth client. public string Privacy { get { return "Privacy statement"; } } // Does nothing, potentially will be removed in a future release public bool Enabled { get { return true; } } // Implemented simply as an auto-property, this may also be removed in the future private string _callbackUrl; public virtual string CallbackUrl { get { return _callbackUrl; } set { if (!string.IsNullOrEmpty(value) && value.StartsWith("http:")) _callbackUrl = "https" + value.Substring(4); else _callbackUrl = value; } }
ClientLogoutScript allows some additional actions to be taken when logout occurs in Community. This is html that will be executed when a client logs out after being logged in through this OAuth client. Use this to perform further actions if needed.
public string ClientLogoutScript { get { return ""; } }
IconUrl is used to display an image when presenting your OAuth client as a login option. It returns the url to an image representing your OAuth provider. You can link to an external url or add an image to Centralized File Storage and link to that.
public string IconUrl { get { return "https://developers.pinterest.com/static/img/badge.svg"; } }
Then is a final "property" that is accessed via the GetAuthorizationLink() method. Again, the query string properties and other formatting would be specified by the third party service. Here, we will not be interacting with any Pinterest data other than the user's identity, so we only need the "read_public" permission.
public string GetAuthorizationLink() { return string.Format("{0}/?client_id={1}&redirect_uri={2}&response_type=code&scope=read_public", AuthorizeBaseUrl, ConsumerKey, Globals.UrlEncode(CallbackUrl)); }
Processing Login Context
The main job of the client is to process a user's login based on information in the HttpContext. First we perform some sanity checks. The existence of errors in the Query string may be dependent on your third party service's implementation.
public OAuthData ProcessLogin(HttpContextBase context) { if (!Enabled || context.Request.QueryString["error"] != null) FailedLogin();
Next we need to check for the data we need for verification of the user login - in this case, an authentication code to be used to gain an access token.
if (context.Request.QueryString["code"] == null) FailedLogin(); string authorizationCode = context.Request.QueryString["code"];
With the authentication code in hand, we then request the access token and make a sanity check.
string token = GetAccessToken(authorizationCode); if (string.IsNullOrEmpty(token)) FailedLogin();
Finally, we query the Pinterest API for user data and return an OAuthData object populated with that data:
return GetUserData(token); }
Getting the Access Token
Getting the access token requires another HTTP request to Pinterest. First we set up the request using the required parameters, data, and request properties. (Other services may vary on the data required here).
private string GetAccessToken(string authorizationCode) { // Use the authorization code to request an access token. string postData = string.Format("grant_type=authorization_code&client_id={0}&client_secret={1}&code={2}", ConsumerKey, ConsumerSecret, authorizationCode); var webRequest = (HttpWebRequest)System.Net.WebRequest.Create(AccessTokenUrl); webRequest.Method = "POST"; webRequest.ServicePoint.Expect100Continue = false; webRequest.Timeout = 20000; webRequest.ContentType = "application/x-www-form-urlencoded";
We then make the request and read the data received.
using (var requestWriter = new StreamWriter(webRequest.GetRequestStream())) { requestWriter.Write(postData); requestWriter.Close(); } string responseData; try { using (var responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream())) { responseData = responseReader.ReadToEnd(); responseReader.Close(); } } catch (Exception ex) { throw new ApplicationException("OAuth Web Request Failed", ex); }
Finally, we can extract the access token from the data and return it.
if (responseData.Length > 0) { //Store the returned access_token dynamic accessResponse = new JavaScriptSerializer().DeserializeObject(responseData); if (accessResponse["access_token"] != null) return accessResponse["access_token"]; } return null; }
Getting Your User's Data
Now we at last have full authorizations to make calls to the Pinterest API on our user's behalf. Our main concern for pure authentication purposes is identifying the user uniquely so Community can track them internally. First we've created a few private classes to help with parsing from json. You can also use the c# dynamic, which we've done above.
private class PinterestUserDetails { public PinterestData data; } private class PinterestData { public string url { get; set; } public string first_name { get; set; } public string last_name { get; set; } public string id { get; set; } }
To get user data, we make a call to the /me endpoint with our access token. Similar to above, we generate the web request, and receive and read the response.
// Use Pinterest API to get details about the user var webRequest = (HttpWebRequest)WebRequest.Create(string.Format("https://api.pinterest.com/v1/me?access_token={0}", token)); webRequest.Method = "GET"; webRequest.ServicePoint.Expect100Continue = false; webRequest.Timeout = 20000; string responseData; try { using (var responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream())) { responseData = responseReader.ReadToEnd(); responseReader.Close(); } } catch (Exception ex) { throw new ApplicationException("OAuth Web Request Failed", ex); }
Parsing the response into our private classes allows easy access to populate the OAuthData return class:
if (responseData.Length > 0) { //Store the returned access_token PinterestUserDetails userDetails = new JavaScriptSerializer().Deserialize<PinterestUserDetails>(responseData); var data = new OAuthData { ClientId = userDetails.data.id, ClientType = ClientType, UserName = string.Format("{0}{1}", userDetails.data.first_name, userDetails.data.last_name), CommonName = string.Format("{0} {1}", userDetails.data.first_name, userDetails.data.last_name) }; return data; } return null;
Seeing the Results
Once you've compiled and added the plugin to your Community instance, you can enable it, input the key and secret, and your users will be able to sign in with Pinterest. There will be an icon on the sign in page, and clicking will begin the login process.
View the full sample project on github: https://github.com/Telligent/Pinterest-OAuth-Client
Best practices
- When accessing a user's private information, it is best to use https instead of http to ensure the user's information is retrieved securely.
- Using configuration options to set the ConsumerKey and ConsumerSecret is preferred to hard coding the values so that if the values change, the OAuth client does not have to be recompiled.