Skip to content

Instantly share code, notes, and snippets.

@ultimatemonty
Last active January 24, 2016 03:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ultimatemonty/cab62f46ccf708ddf2f1 to your computer and use it in GitHub Desktop.
Save ultimatemonty/cab62f46ccf708ddf2f1 to your computer and use it in GitHub Desktop.
ServiceStack Custom Auth
using System.Configuration;
using Funq;
using ServiceStack;
using ServiceStack.Admin;
using ServiceStack.Auth;
using ServiceStack.Caching;
using ServiceStack.Data;
using ServiceStack.Validation;
using ServiceStack.OrmLite;
using ServiceStack.Web;
using DemoApp.API.Core;
using DemoApp.API.ServiceModel.Application;
using DemoApp.API.ServiceInterface;
using DemoApp.API.ServiceInterface.Application;
using DemoApp.API.ServiceInterface.Auth;
using DemoApp.API.ServiceInterface.Billing;
using System;
using System.Data.SqlClient;
namespace DemoApp.API
{
public class AppHost : AppHostBase
{
EventLogger _logger = new EventLogger("DemoApp API");
/// <summary>
/// Default constructor.
/// Base constructor requires a name and assembly to locate web service classes.
/// </summary>
public AppHost() : base("DemoApp REST API", typeof(Customers).Assembly){}
/// <summary>
/// Application specific configuration
/// This method should initialize any IoC resources utilized by your web service classes.
/// </summary>
/// <param name="container"></param>
public override void Configure(Container container)
{
this.ServiceExceptionHandlers.Add((httpReq, request, exception) =>
{
_logger.Error(exception);
return DtoUtils.CreateErrorResponse(request, exception);
});
//Custom global uncaught exception handling strategy
this.UncaughtExceptionHandlers.Add((req, res, operationName, ex) =>
{
res.StatusCode = 500;
res.Write(string.Format("{0}: {1}", ex.GetType().Name, ex.Message));
res.EndRequest(skipHeaders: true);
});
// CamelCasing in JSON responses
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Postman Support
Plugins.Add(new PostmanFeature {
EnableSessionExport = true
});
// Fluent Validation
Plugins.Add(new ValidationFeature());
// CORS
Plugins.Add(new CorsFeature(
allowedOrigins: "*",
allowedHeaders: "Content-Type, Authorization",
allowCredentials: false
));
this.GlobalRequestFilters.Add((httpReq, httpRes, requestDto) =>
{
if (httpReq.Verb == "OPTIONS")
{
httpRes.EndRequest();
}
});
// caching
container.Register<ICacheClient>(new MemoryCacheClient());
// Form based credentials authentication
this.Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
// more providers can be added here as required
new DemoCredentialsAuthProvider() {SessionExpiry = new TimeSpan(3,0,0)}
}
));
container.Register<IUserAuthRepository>(c => new InMemoryAuthRepository());
}
//Initialize your application singleton
protected void Application_Start(object sender, EventArgs e)
{
new AppHost().Init();
}
public override void OnExceptionTypeFilter(Exception ex, ResponseStatus responseStatus)
{
var sqlEx = ex as SqlException;
if (sqlEx != null)
{
string errorMessage;
switch (sqlEx.Number)
{
case 2627:
case 2601:
errorMessage = "This record already exists";
break;
default:
if (String.IsNullOrEmpty(sqlEx.Message))
{
errorMessage = "A database error occurred";
}
else
{
errorMessage = sqlEx.Message;
}
break;
}
responseStatus.Errors.Add(new ResponseError
{
ErrorCode = ex.GetType().Name,
Message = errorMessage,
});
}
}
public override IAuthSession OnSessionFilter(IAuthSession session, string id)
{
return base.OnSessionFilter(session, id);
}
}
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Web;
using ServiceStack;
using ServiceStack.Caching;
using ServiceStack.Auth;
using ServiceStack.Configuration;
using ServiceStack.FluentValidation;
using ServiceStack.Web;
using DemoApp.API.Core;
using DemoApp.API.Core.SDK;
using DemoApp.API.Core.Application;
using DemoApp.API.Core.Billing;
namespace DemoApp.API.ServiceInterface.Auth
{
public class DemoCredentialsAuthProvider : CredentialsAuthProvider
{
PubSub _pubsub = new PubSub();
private int _adminUserId;
private string _salt;
private string _passwordHash;
private string _customerId;
private string _subscriptionId;
private User _user;
private class CredentialsAuthValidator : AbstractValidator<Authenticate>
{
public CredentialsAuthValidator()
{
RuleFor(x => x.UserName).NotEmpty();
RuleFor(x => x.Password).NotEmpty();
}
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
// some basic validation on the request
new CredentialsAuthValidator().ValidateAndThrow(request);
try
{
string username = request.UserName;
string password = request.Password;
string referrerUrl = request.Continue;
// get the user data from the DB
this._getUserFromDB(username);
// validate that the credentials are valid
if (this._validateCredentials(password))
{
session.IsAuthenticated = true;
// validate that the subscription is valid
if(this._validateSubscription())
{
// save the session
authService.SaveSession(session, SessionExpiry);
return new CustomAuthenticateResponse
{
UserId = _user.Id.ToString(),
UserName = _user.UserName,
SessionId = session.Id,
ReferrerUrl = session.ReferrerUrl,
SubscriptionValid = true
};
}
else
{
throw HttpError.Unauthorized("Your DemoApp subscription is out of date. Please contact your administrator");
}
}
}
catch (Exception ex)
{
throw HttpError.NotFound(ex.Message);
}
// failed to authenticate - throw an error
throw HttpError.Unauthorized(ErrorMessages.InvalidUsernameOrPassword);
}
private void _getUserFromDB(string username)
{
try
{
Query query = new Query("SelectUserForAuthentication");
query.AddParameter("username", username);
DataRow result = query.ReturnRow();
this._user = new User();
if (DatabaseUtils.SQLInteger(result["id"]) > 0)
{
// valid result returned
this._user.Id = DatabaseUtils.SQLInteger(result["id"]);
this._user.UserName = username;
this._user.FirstName = DatabaseUtils.SQLString(result["firstName"]);
this._user.LastName = DatabaseUtils.SQLString(result["lastName"]);
this._user.Location = DatabaseUtils.SQLInteger(result["defaultLocation"]);
this._user.CreatedDate = DatabaseUtils.SQLDateTime(result["CreatedDate"]).ToISOZString();
this._passwordHash = DatabaseUtils.SQLString(result["passwordHash"]);
this._salt = DatabaseUtils.SQLString(result["salt"]);
this._customerId = DatabaseUtils.SQLString(result["CustomerId"]);
this._adminUserId = DatabaseUtils.SQLInteger(result["AdminId"]);
this._subscriptionId = DatabaseUtils.SQLString(result["SubscriptionId"]);
}
}
catch (Exception ex)
{
throw;
}
}
private bool _validateCredentials(string password)
{
SaltedHash saltedHash = new SaltedHash();
// verify the passwords match
if (saltedHash.VerifyHashString(password, this._passwordHash, this._salt))
{
return true;
}
return false;
}
private bool _validateSubscription()
{
try
{
if (!this._customerId.IsNullOrEmpty())
{
BillingProvider billingApi = new BillingProvider();
DemoSubscription subscription = billingApi.LookupSubscription(this._customerId, this._subscriptionId);
if (subscription.status != DemoSubscriptionStatus.Cancelled)
{
return true;
}
}
return false;
}
catch (Exception ex)
{
throw;
}
}
}
public class CustomAuthenticateResponse : AuthenticateResponse
{
public bool SubscriptionValid { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment