-
-
Save ultimatemonty/cab62f46ccf708ddf2f1 to your computer and use it in GitHub Desktop.
ServiceStack Custom Auth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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