A cross platform framework for collecting personal data from various sources including web apis (OAuth), bluetooth devices, and smartphone embedded sensors (GPS, SMS, etc)
You can get Material by grabbing the latest NuGet package currently available for .NET 4.5, Xamarin.Android, Xamarin.iOS, and Windows UWP (Xamarin.Forms in progress)
- I have a mobile/desktop app and I want to use an OAuth1 authentication provider (ie Login with Twitter)
- I have a mobile/desktop app and I want to use an OAuth2 authentication provider (ie Login with Facebook)
- I have a web application and I want to use an OAuth1 authentication provider (ie Login with Twitter)
- I have a web application and I want to use an OAuth2 authentication provider (ie Login with Facebook)
- I have a mobile, web or desktop app and I want to refresh my OAuth2 credentials
- I have a mobile, web or desktop app and I want to access OAuth protected resources (ie access to my users Tweets)
- I want to test OAuth2 authentication workflows for an OAuth2 provider I am creating
- I want to connect to a Bluetooth GATT device
- I want to consume a device resource (SMS or GPS)
Twitter
Fatsecret
Withings
- Create a Provider
Facebook
(token expires, no refresh token provided)Fitbit
(token expires, refresh token provided)Foursquare
(token does not expire)Google
(token expires, refresh token provided)LinkedIn
(token does not expire)Rescuetime
(token does not expire)Runkeeper
(token does not expire)Spotify
(token expires, refresh token provided)TwentyThreeAndMe
(token expires, refresh token provided)- Create a Provider
FacebookFeed
FacebookEvent
FacebookFriend
FacebookPageLike
FatsecretMeal
FitbitIntradayHeartRate
FitbitIntradayHeartRateBulk
FitbitIntradaySteps
FitbitIntradayStepsBulk
FitbitProfile
FitbitSleep
FoursquareCheckin
FoursquareFriend
FoursquareTip
GoogleGmail
GoogleGmailMetadata
LinkedinPersonal
LinkedinUpdate
RescuetimeAnalyticData
RunkeeperFitnessActivity
SpotifySavedTrack
TwentyThreeAndMeGenome
TwentyThreeAndMeUser
TwitterFavorite
TwitterFollower
TwitterFollowing
TwitterMention
TwitterReceivedDirectMessage
TwitterSentDirectMessage
TwitterRetweetOfMe
TwitterTimeline
TwitterTweet
WithingsWeighin
- Create a Request
To obtain the redirect url for authorization on the resource providers server:
//OPTIONALLY: inject the OAuth1Web instance into your class or method
string consumerKey = "YOUR CONSUMER KEY";
string consumerSecret = "YOUR CONSUMER SECRET"
string callbackUri = "HTTP://YOURCALLBACKURI";
OAuth1Web<Twitter> oauth = new OAuth1Web<Twitter>(
consumerKey,
consumerSecret,
callbackUri);
string userId = "SOMEUSERID"; //some unique identifier stored in a cookie or session state
Uri twitterLoginEndpoint = await oauth
.GetAuthorizationUriAsync(userId)
.ConfigureAwait(false);
To handle the callback to your server (assume this callback is an endpoint TwitterCallback
within an .NET MVC Controller
):
public async Task<ActionResult> TwitterCallback()
{
//SUGGESTED: inject the OAuth1Web instance into your controller
string clientId = "YOUR CLIENT ID";
string clientSecret = "YOUR CLIENT SECRET";
string callbackUri = "HTTP://YOURCALLBACKURI";
OAuth1Web<Twitter> oauth = new OAuth1Web<Twitter>(
clientId,
clientSecret,
callbackUri);
string userId = Request.Cookies["userId"];
string url = ControllerContext.HttpContext.Request.Url;
OAuth1Credentials intermediateCredentials = oauth
.ParseAndValidateCallback(url, userId);
OAuth1Credentials credentials = await oauth
.GetAccessTokenAsync(intermediateCredentials)
.ConfigureAwait(false);
//you've got the credentials!
return RedirectToAction("SOMEREDIRECTPAGE");
}
* If you are working in an environment with multiple servers and you do not use sticky sessions, see the advanced topic Creating Your Own Security Strategy
To obtain the redirect url for authorization on the resource providers server:
//OPTIONALLY: inject the OAuth2Web instance into your class or method
string clientId = "YOUR CLIENT ID";
string clientSecret = "YOUR CLIENT SECRET";
string callbackUri = "HTTP://YOURCALLBACKURI";
OAuth2Web<Facebook> oauth = new OAuth2Web<Facebook>(
clientId,
clientSecret,
callbackUri);
string userId = "SOMEUSERID"; //some unique identifier stored in a cookie or session state
Uri facebookLoginEndpoint = await oauth
.GetAuthorizationUriAsync(userId)
.ConfigureAwait(false);
If you want to make a subsequent request for a protected resource add scopes for each request you intend to make. For example:
Uri facebookLoginEndpoint = await oauth
.AddScope<FacebookFeed>()
.AddScope<FacebookFriend>()
.GetAuthorizationUriAsync(userId)
.ConfigureAwait(false);
To handle the callback to your server (assume this callback is an endpoint FacebookCallback
within an .NET MVC Controller
and that the userId is stored in a cookie):
public async Task<ActionResult> FacebookCallback()
{
//SUGGESTED: inject the OAuth2Web instance into your controller
string clientId = "YOUR CLIENT ID";
string clientSecret = "YOUR CLIENT SECRET";
string callbackUri = "HTTP://YOURCALLBACKURI";
OAuth2Web<Facebook> oauth = new OAuth2Web<Facebook>(
clientId,
clientSecret,
callbackUri);
var userId = Request.Cookies["userId"];
var url = ControllerContext.HttpContext.Request.Url;
OAuth2Credentials intermediateCredentials = oauth
.ParseAndValidateCallback(url, userId);
OAuth2Credentials credentials = await oauth
.GetAccessTokenAsync(intermediateCredentials)
.ConfigureAwait(false);
//you've got the credentials!
return RedirectToAction("SOMEREDIRECTPAGE");
}
* If you are working in an environment with multiple servers and you do not use sticky sessions, see the advanced topic Creating Your Own Security Strategy
When creating your app with the resource provider use localhost as the callback uri with an uncommon port number. For example http://localhost:33533/twitter
public async Task MyAuthenticationMethod()
{
string consumerKey = "YOUR CONSUMER KEY";
string consumerSecret = "YOUR CONSUMER SECRET";
string callbackUri = "http://localhost:33533/twitter";
OAuth1App<Twitter> oauth1 = new OAuth1App<Twitter>(
consumerKey,
consumerSecret,
callbackUri);
OAuth1Credentials credentials = await oauth1
.GetCredentialsAsync()
.ConfigureAwait(false);
}
When creating your app with the resource provider use localhost as the callback uri with an uncommon port number. For example http://localhost:33533/facebook
public async Task MyAuthenticationMethod()
{
string clientId = "YOUR CLIENT ID";
string callbackUri = "http://localhost:33533/facebook";
OAuth2App<Facebook> oauth2 = new OAuth2App<Facebook>(
clientId,
callbackUri);
OAuth2Credentials credentials = await oauth2
.GetCredentialsAsync()
.ConfigureAwait(false);
}
For subsequent requests of protected resource add scopes for each request type. For example:
OAuth2Credentials credentials = await oauth
.AddScope<FacebookFeed>()
.AddScope<FacebookFriend>()
.GetCredentialsAsync()
.ConfigureAwait(false);
The 'code' workflow can also be used in the event a long lived access token is desired and the client secret can be embedded in the application (testing purposes, etc):
public async Task MyAuthenticationMethod()
{
string clientId = "YOUR CLIENT ID";
string clientSecret = "YOUR CLIENT SECRET";
string callbackUri = "HTTP://YOURCALLBACKURI";
OAuth2App<Facebook> oauth2 = new OAuth2App<Facebook>(
clientId,
clientSecret,
callbackUri);
OAuth2Credentials credentials = await oauth2
.GetCredentialsAsync()
.ConfigureAwait(false);
}
By default OAuth1App
and OAuth2App
use an embedded browser (WebView
on Android, UIWebView
on iOS, WebView
on UWP) to complete the oauth workflow. To use a dedicated (system) browser see the section on Dedicated Mobile Browsers.
If the authentication provider has an access token that expires and provides a refresh token (see list of OAuth2 providers), that refresh token can be exchanged for a new, valid access token:
OAuth2Credentials googleCredentials = CREDENTIALS_I_GOT_EARLIER;
if (googleCredentials.IsTokenExpired)
{
googleCredentials = await new OAuth2Refresh<Google>()
.RefreshCredentialsAsync(
expiredToken)
.ConfigureAwait(false);
}
After gathering the necessary OAuth1Credentials
by using either the OAuth 1 web or OAuth 1 desktop workflows, or OAuth2Credentials
by using either the OAuth 2 web or OAuth 2 desktop workflows, a request for a protected resource can be made.
OAuth1Credentials twitterCredentials = CREDENTIALS_I_GOT_EARLIER;
var response = await new OAuthRequester(twitterCredentials)
.MakeOAuthRequestAsync<TwitterTweet, TwitterTweetResponse>()
.ConfigureAwait(false);
If the request needs to be customized an instance of the request class can be created
OAuth1Credentials twitterCredentials = CREDENTIALS_I_GOT_EARLIER;
var request = new TwitterTweet();
request.Count = 100;
var response = await new OAuthRequester(twitterCredentials)
.MakeOAuthRequestAsync<TwitterTweet, TwitterTweetResponse>(request)
.ConfigureAwait(false);
BluetoothCredentials credentials = await new BluetoothApp<Mioalpha>()
.GetBluetoothCredentialsAsync()
.ConfigureAwait(false);
BluetoothResponse result = await new BluetoothRequester()
.MakeBluetoothRequestAsync<MioHeartRate>(credentials)
.ConfigureAwait(false);
To connect to a custom Bluetooth GATT device, see Creating a Bluetooth Provider and Request
To request the current GPS position:
GPSResponse result = await new GPSRequester()
.MakeGPSRequestAsync()
.ConfigureAwait(false);
(Android only) To request a list of SMS currently in inbox or sent:
SMSResponse results = await new SMSRequester()
.MakeSMSRequestAsync()
.ConfigureAwait(false);
The SMS results can also be filtered by a date:
DateTime dateFilter = System.DateTime.Today;
SMSResponse results = await new SMSRequester()
.MakeSMSRequestAsync(dateFilter)
.ConfigureAwait(false);
Since rescuetime requires an HTTPS endpoint and the current HttpServer implementation does not handle HTTPS you will see an error when your Rescuetime callback request comes back, when using a desktop workflow. The current workaround is for the user to manually update the url in the browser window, changing HTTPS into HTTP and then hitting 'return'.
WORK IN PROGRESS: Currently performing this configuration only works with Google and requires particular setup steps in the configurations of the iOS, Android, or UWP project to properly receive the protocol based callback
In some mobile device situations a dedicated browser (Chrome on Android, Safari on iOS, IE on Windows) may be desired for the workflow. If that is the case an optional parameter can be passed to indicate the browser type:
OAuth1App<Twitter> oauth1 = new OAuth1App<Twitter>(
consumerKey,
consumerSecret,
callbackUri,
AuthenticationInterfaceEnum.Dedicated);
OAuth2App<Facebook> oauth2 = new OAuth2App<Facebook>(
clientId,
clientSecret,
callbackUri,
AuthenticationInterfaceEnum.Dedicated);
During the OAuth2 workflow a InMemoryCryptographicParameterRepository
object is used to store the "state" parameter that is round-tripped to the resource provider. This implementation stores the generated parameters in a static variable in the current app domain. This is problematic in a multi-server scenario without sticky sessions. To remedy this create an implementation of ICryptographicParameterRepository
that utilizes some other mechanism of storing the parameters (database session cache, cookies, etc). For example:
public class CookieCryptographicParameterRepository :
ICryptographicParameterRepository
{
private readonly HttpCookieCollection _cookies;
public CookieCryptographicParameterRepository(
HttpCookieCollection cookies)
{
_cookies = cookies;
}
public void SetCryptographicParameterValue(
string userId,
string parameterName,
string parameterValue,
DateTimeOffset timestamp)
{
var cookie = new HttpCookie(userId + parameterName);
cookie.Values["value"] = parameterValue;
cookie.Values["timestamp"] = timestamp.ToString();
_cookies.Add(cookie);
}
public Tuple<string, DateTimeOffset> GetCryptographicParameterValue(
string userId,
string parameterName)
{
var cookie = _cookies[userId + parameterName];
if (cookie == null)
{
return default(Tuple<string, DateTimeOffset>);
}
else
{
return new Tuple<string, DateTimeOffset>(
cookie["value"],
DateTimeOffset.Parse("timestamp"));
}
}
public void DeleteCryptographicParameterValue(
string userId,
string parameterName)
{
_cookies.Remove(userId + parameterName);
}
}
When creating an instance of OAuth2WebFacade
, pass an instance of the repository to OAuthSecurityStrategy
and then into the facade:
string clientId = "YOUR CLIENT ID";
string clientSecret = "YOUR CLIENT SECRET";
string callbackUri = "HTTP://YOURCALLBACKURI";
string userId = "SOMEUSERID";
OAuthSecurityStrategy strategy = new OAuthSecurityStrategy(
new CookieCryptographicParameterRepository(
HttpContext.Response.Cookies),
TimeSpan.FromMinutes(2)))
OAuth2WebFacade<Facebook> oauth2 = new OAuth2WebFacade<Facebook>(
clientId,
clientSecret,
userId,
callbackUri,
strategy);
Create a resource provider class to acquire the device address:
using Material.Infrastructure.Credentials;
using Material.Metadata;
namespace Material.Infrastructure.ProtectedResources
{
[CredentialType(typeof(BluetoothCredentials))]
public partial class MyBluetoothDevice : BluetoothResourceProvider
{
}
}
Create a request to access the GATT characteristic. The static classes BluetoothServices
and BluetoothCharacteristics
contain the assigned numbers for all approved bluetooth services and characteristics respectively. A conversion function should be written on a characteristic by characteristic basis to convert the raw byte[]
reading into a string.
using System;
using Material.Infrastructure.Bluetooth;
using Material.Infrastructure.ProtectedResources;
using Material.Metadata;
namespace Material.Infrastructure.Requests
{
[ServiceType(typeof(MyBluetoothDevice))]
public partial class MyBluetoothDeviceBloodPressureRate : BluetoothRequest
{
public override BluetoothSpecification Characteristic =>
BluetoothCharacteristics.BloodPressureMeasurement;
public override Func<byte[], string> CharacteristicConverter =>
(data) => data.ToString();
public override BluetoothSpecification Service =>
BluetoothServices.BloodPressure;
}
}
TODO
TODO