Oauth provides users a way to login to Telligent Community using credentials from other popular websites that support the OAuth standard, such as Facebook or Twitter.
Developers can add additional login options for other sites that support OAuth. The providers that come with Telligent Community collect only the information needed from a user to log them into the site.
[toc]
Getting Started
Implementing IOAuthClient provides the capability to use a third party service to provide user authentication. Our sample implementation 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
Existing Clients
Telligent Community already 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. You only need to implement a custom client if you are not planning on using one these sites/services. Note that the service with which you plan to implement IOAuthClient must support the OAuth standard.
- Facebook authentication
- Google authentication
- LinkedIn authentication
- Live Connect authentication
- Salesforce authentication
- Twitter authentication
Defining Client Properties
We define several properties from the interface as constant values for later use.
Client Name is the name used by Telligent Community when referring to your client - for instance, as mouseover text on your icon.
public string ClientName { get { return "Pinterest"; } }
Client Type must be a unique value among all OAuth Clients, identifying your client.
public string ClientType { get { return "pinterest"; } }
The AuthorizeBaseUrl and AccessTokenUrl should be obtained from the third party service you are authenticating against.
public virtual string AuthorizeBaseUrl { get { return "https://api.pinterest.com/oauth"; } } public virtual string AccessTokenUrl { get { return "https://api.pinterest.com/v1/oauth/token"; } }
Theme Color is a hex-based color value that provides additional identity to your client for display purposes. While not used in the current default Social theme, this property could be used by a custom theme.
public string ThemeColor { get { return "BD081C"; } }
ConsumerKey and ConsumerSecret should be generated for you when you create an app with the external service. Use of IConfigurablePlugin is recommended here, so that the values can be edited via Administration in your Telligent Community site.
public virtual string ConsumerKey { get { return Configuration.GetString("ConsumerKey"); } } public virtual string ConsumerSecret { get { return Configuration.GetString("ConsumerSecret"); } }
Privacy Statement is simply a text field to allow you to provide legal information to your users regarding use of this login method.
// 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"; } }
Enabled and CallbackUrl are both managed by Telligent Community. Enabled can be hard-coded to true and CallbackUrl implemented as a standard property with validation that any new CallbackUrl value be https.
public bool Enabled { get { return true; } } 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 Telligent Community. This is javascript 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. The implementation for Pinterest does not need to use it, so we've left it blank.
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"; } }
The GetAuthorizationLink() method simply provides the URL to be used to initially request an access token to the external site. 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
The main job of the client is to process a user's login based on information in the response from the external service - in this case, Pinterest. 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"];
Getting the Access Token
Once the authentication code is in hand, we can use this to obtain an access token. The access token itself will allow us to access Pinterest resources securely. From the ProcessLogin()
method we call a private method to get the access token:
string token = GetAccessToken(authorizationCode);
If we examine this method further you can see that we are using the obtained authentication code to make another HTTP request to Pinterest in order to obtain the access token. First we set up the request using the required parameters, data, and request properties. (Other services may vary on the data required here). We will then read the response to extract the access token we will use to obtain information about your Pinterest account. Notice to make reading the token easier we used a new .NET 4.5 feature called dynamic. This allows us to avoid creating a class to deserialize to, especially since the response is relatively simple.
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 webClient = new WebClient(); string responseData; try { webClient.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; responseData = webClient.UploadString(AccessTokenUrl, postData); } catch (Exception ex) { throw new ApplicationException("OAuth Web Request Failed", ex); } 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 authorization to make calls to the Pinterest API on our user's behalf using the access token we requested previously. While the OAuth flow tells us the user exists in Pinterest and has successfully authenticated through them, it does not provide Telligent Community enough information to establish an account. To do this we must obtain more information from Pinterest about the user, minimally enough to provide to establish a unique username. It is also desirable to obtain an email address if the service supports providing it(Pinterest does not), though it is not required. If an email address is not available Telligent Community will prompt a user for one. Email addresses and Usernames in Telligent Community must be unique. You can see we also extracted this process into its own method we call as we return from ProcessLogin()
.
return GetUserData(token);
First create custom classes that represent the response from the Pinterest me REST endpoint. You could use the dynamic object approach described earlier if you choose but in this case we will create concrete classes to deserialize to:
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; } }
The concepts illustrated by the GetUserData()
method are similar to obtaining an access token but instead we are getting information from a Pinterest REST API. We will make an HTTP request to the me
REST API at Pinterest, using the access token we obtained to authenticate the request, de-serialize the response using our custom classes and then use that information to populate an OauthData
object that will be used by Telligent Community to both create an account and link the Pinterest account to the new Telligent Community account for later use:
private OAuthData GetUserData(string token) { // Use Pinterest API to get details about the user string pinterestUrl = string.Format("https://api.pinterest.com/v1/me?access_token={0}", token); var webClient = new WebClient(); string responseData; try { responseData = webClient.DownloadString(pinterestUrl); } catch (Exception ex) { throw new ApplicationException("OAuth Web Request Failed", ex); } 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; }
Minimally the following information must be set on the OAuthData object:
- ClientId - Most services have an id or some value that uniquely identifies a user in that system. That value should be used here. It should be unique per client type. Along with ClientType it will be used to identify a user who has already authenticated using this client.
- ClientType - This identifies the Oauth client itself and should simply be set to the ClientType property of the IOAuthClient implementation it is used in.
- UserName - This is the username that will be assigned to the user in the community and must be unique.
There is also an Email property that should be set if the service makes it available as mentioned previously. Otherwise Telligent Community will ask for an email address the first time the client is used.
We also populated an optional parameter on OauthData
called CommonName. If you provide a value here it will set the user's DisplayName which is a more friendly way of identifying a user versus showing the username. It does not have to be unique.
Seeing the Results
Once you've compiled and added the plugin to your Telligent 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.
Full Sample
View the full sample project on github: https://github.com/Telligent/Pinterest-OAuth-Client