Skip to content

Instantly share code, notes, and snippets.

@codeprogression
Created June 22, 2012 21:45
Show Gist options
  • Save codeprogression/2975398 to your computer and use it in GitHub Desktop.
Save codeprogression/2975398 to your computer and use it in GitHub Desktop.
OAuth2 Authentication (Consumer) for NancyFX
public class OAuth2AuthenticationClient
{
public static void Enable(IPipelines pipelines, OAuth2AuthenticationClientConfiguration clientConfiguration)
{
if (!clientConfiguration.IsValid)
throw new OAuth2AuthenticationClientConfiguration.InvalidConfigurationException();
pipelines.BeforeRequest.AddItemToStartOfPipeline(GetLoadAuthenticationHook(clientConfiguration));
pipelines.AfterRequest.AddItemToEndOfPipeline(GetRedirectToProviderHook(clientConfiguration));
}
public static void Enable(NancyModule module, OAuth2AuthenticationClientConfiguration clientConfiguration)
{
if (!clientConfiguration.IsValid)
throw new OAuth2AuthenticationClientConfiguration.InvalidConfigurationException();
module.RequiresAuthentication();
module.Before.AddItemToStartOfPipeline(GetLoadAuthenticationHook(clientConfiguration));
module.After.AddItemToEndOfPipeline(GetRedirectToProviderHook(clientConfiguration));
}
static Func<NancyContext, Response> GetLoadAuthenticationHook(OAuth2AuthenticationClientConfiguration clientConfiguration)
{
if (clientConfiguration == null)
{
throw new ArgumentNullException("clientConfiguration");
}
return context =>
{
//need implementation here
return null;
};
}
static bool EnsureIsValidAuthorization(NancyContext context)
{
return ((string)context.Request.Query.State).Replace(" 0", "+") == context.Request.Session["state"].ToString();
}
static Action<NancyContext> GetRedirectToProviderHook(OAuth2AuthenticationClientConfiguration clientConfiguration)
{
return context =>
{
if (context.Response.StatusCode == HttpStatusCode.Unauthorized)
{
var sessionKey = clientConfiguration.GetNewStateValue();
context.Request.Session["state"] = sessionKey;
context.Response = context.GetRedirect(clientConfiguration.GetRequestUri(sessionKey));
}
};
}
}
public class OAuth2AuthenticationClientConfiguration
{
/// <summary>
/// Gets or sets the cryptography configuration
/// </summary>
public CryptographyConfiguration CryptographyConfiguration { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuth2AuthenticationClientConfiguration"/> class.
/// </summary>
public OAuth2AuthenticationClientConfiguration()
: this(CryptographyConfiguration.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OAuth2AuthenticationClientConfiguration"/> class.
/// </summary>
/// <param name="cryptographyConfiguration">Cryptography configuration</param>
public OAuth2AuthenticationClientConfiguration(CryptographyConfiguration cryptographyConfiguration)
{
CryptographyConfiguration = cryptographyConfiguration;
}
/// <summary>
/// A unique value used by your application in order to prevent cross-site request forgery (CSRF).
/// The value should be random, unguessable, specific for the particular request and kept secret
/// in the client (perhaps server-side session).
///
/// This method provides a HMAC encoded string from a random 9-byte key
/// </summary>
public string GetNewStateValue()
{
return
Convert.ToBase64String(
CryptographyConfiguration.HmacProvider.GenerateHmac(new RandomKeyGenerator().GetBytes(9)));
}
/// <summary>
/// The endpoint to send an OAuth2 authorization request.
/// </summary>
public string AuthorizationEndpoint { get; set; }
/// <summary>
/// The value provided when you registered your application with the provider.
/// </summary>
public string ClientId { get; set; }
/// <summary>
/// The value provided when you registered your application with the provider.
/// </summary>
public string ClientSecret { get; set; }
/// <summary>
/// The location the user should be returned to after they approve access to your app.
/// This value typically needs to be registered in advance with the provider.
/// </summary>
public string RedirectUri { get; set; }
/// <summary>
/// The data your application is requesting access to.
/// Usually space-delimited strings. (Depending on provider, may be comma-delimited, e.g. Facebook)
/// </summary>
public string Scope { get; set; }
/// <summary>
/// Specifies the application flow.
/// For a server-side web application flow, use `code`
/// </summary>
public string ResponseType { get; set; }
string State { get; set; }
public NameValueCollection CustomRequestParameters { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrEmpty(AuthorizationEndpoint)
&& !string.IsNullOrEmpty(CallbackRoute)
&& !string.IsNullOrEmpty(ClientId)
&& !string.IsNullOrEmpty(ClientSecret)
&& !string.IsNullOrEmpty(ResponseType)
&& !string.IsNullOrEmpty(RedirectUri);
}
}
/// <summary>
/// The route that an OAuth provider can redirect authorization code to
/// </summary>
public string CallbackRoute { get; set; }
/// <summary>
///
/// </summary>
/// <param name="sessionKey"></param>
/// <returns></returns>
public virtual string GetRequestUri(string sessionKey)
{
var query = HttpUtility.ParseQueryString("");
query.Add("client_id",ClientId);
query.Add("redirect_uri", RedirectUri);
query.Add("scope", Scope);
query.Add("response_type", ResponseType);
query.Add("state", sessionKey);
if (CustomRequestParameters!=null)
query.Add(CustomRequestParameters);
var uri = new UriBuilder(AuthorizationEndpoint)
{
Query = EncodeQueryString(query)
};
return uri.ToString();
}
static string EncodeQueryString(NameValueCollection collection)
{
int count = collection.Count;
if (count == 0)
return "";
var sb = new StringBuilder();
string[] keys = collection.AllKeys;
for (int i = 0; i < count; i++)
{
sb.AppendFormat("{0}={1}&", keys[i], collection[keys[i]]);
}
if (sb.Length > 0)
sb.Length--;
return sb.ToString();
}
public class InvalidConfigurationException : Exception
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment