Caching public JWT authentication keys in Azure Function - azure

I'm having an Azure function .NET core 2.1 with various endpoints.
For Authentication I'm having an external security provider from where I need to retrieve the discovery document (metadata) before being able to validate a request's token.
The document is currently getting loaded every time a single request is made:
var discoveryDocument = await configurationManager.GetConfigurationAsync();
Now I naturally don't want to make this call for every single request due to the fact that the document changes rarely.
On the other hand Azure functions are stateless. I heard of ConfigurationManager.GetConfigurationAsync() is caching the retrieved data somehow, though I couldn't find more information on this. Currently I ended up with a function which gets triggered on startup once, retrieves the document and stores it in the local filesystem. So when a request is made, I'm reading the file again just to avoid another request for getting the public keys.
Any experience on this?
Solution:
I could solve it with a static class as #juunas suggested.
For every single function I am re-using the ValidateToken() method. The call for the discovery document is made every once in a while (when IConfigurationManager thinks it needs to be refreshed) because it is getting cached automatically.
class AuthConfig
{
static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
static AuthConfig()
{
_configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"<CONFIGURL>",
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
}
public static async Task<MyUserEntity> ValidateToken(string token)
{
var discoveryDocument = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters
{
RequireExpirationTime = true,
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = discoveryDocument.SigningKeys,
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = true,
ValidIssuer = discoveryDocument.Issuer
};
try
{
var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var rawValidatedToken);
var user = ParseMyUserEntity(claimsPrincipal);
return user;
}
catch
{
return null;
}
}
}

You can store the data in a static field (which can be in a separate static class too).
That should work as an in-memory cache on that function instance at least.
If the function gets scaled to multiple instances, their caches will be separated.

Related

Error occuriring with call to user authenticated http trigger

I am using Azure Functions v3
I am trying to use Authentication and I have set my function to User level security for its HttpTriggers
The logic below is called on the startup of my function
protected override void SetupAuthentication(
IServiceCollection services, IConfiguration configuration)
{
var tokenOptions = configuration.GetSection("JwtIssuerOptions")
.Get<TokenConfiguration>();
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenOptions.SecurityKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = tokenOptions.Issuer,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = tokenOptions.Audience,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.Configure<IdentityConfiguration>(configuration.GetSection("IdentityConfiguration"));
services.AddScoped<CustomJwtBearerEvents>();
services
.AddAuthentication(o =>
{
o.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = tokenValidationParameters;
options.EventsType = typeof(CustomJwtBearerEvents);
});
}
When I call the function externally I get the error
No authentication handler is registered for the scheme 'WebJobsAuthLevel'.
The registered schemes are: Bearer. Did you forget to call AddAuthentication().AddSomeAuthHandler?.
What have I missed?
I need to mimic the same convention as web apps
[FunctionName("GetPayments")]
public async Task<List<PaymentDto>> GetPaymentsAsync(
[HttpTrigger(AuthorizationLevel.User, "post", Route = "payments/get-payments")]
HttpRequest req,
ILogger log)
{
var data = await req.ReadAsStringAsync();
//THis is where I have my logic which I only want to be able to access if the user has permissions
}
I have seen the link below
https://damienbod.com/2020/09/24/securing-azure-functions-using-azure-ad-jwt-bearer-token-authentication-for-user-access-tokens/comment-page-1/?unapproved=127819&moderation-hash=3fdd04b596812933c4c32e8e8c8cf26a#comment-127819
It initially looked to be what I need, but I cant work out how to adapt it so that it just uses the identity token validation side
Any help would be appreciated
Paul
Have a look at: https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
The latest version ensures all built-in authentication is in place before you add your own or extend the built-in ones. By the way JTW Bearer is already configured by the Functions runtime.
All you need to do is call in your method
services.AddFunctionsAuthentication();
services.AddFunctionsAuthorization();
and then chain whatever other schemes you need to configure after the AddFunctionsAuthentication() call. The resulting authentication builder is designed to handle modifications to the jwt bearer (AddJwtBearer(options => ...) and will not break telling you the Bearer scheme already exists.
This package also gives you the ability to use the FunctionsAuthorize attribute to handle granular authorization requirements for your HTTP functions. Here is a blog post with details: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3

ServiceStack MQ: how to populate data in RequestContext

I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.
The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}

Using Azure B2C with an MVC app gets into infinite loop resulting with Bad Request - Request Too Long Http 400 error

So I've built and published a new website that uses Azure B2C as the authentication mechanism.
What I found was that the login and sign would work fine for a while. But after a period of time, say couple of hours after visiting the site post deployment, I would find that on login or signup, after successful authentication, instead of being redirected back to the return url set up in the b2c configuration, my browser would get caught between an infinite loop between the post authentication landing page that is protected with an authorise attribute and the Azure B2C Login page, before finally finishing with Http 400 error message with the message - Bad Request - Request too long.
I did some googling around this and there are number of posts that suggest that the problem is with the cookie, and that deleting the cookie should resolve the issue. This is not the case. The only thing I have found to fix this is restarting the application on the webserver, or waiting say 24 hours for some kind of cache or application pool to reset. Anyone has any ideas what's going on here?
Ok, I think I may have found the answer.
Looks like there is an issue with Microsoft.Owin library and the way it sets cookies. Writing directly to System.Web solves this problem according to this article.
There are three suggested solutions:
Ensure session is established prior to authentication: The conflict between System.Web and Katana cookies is per request, so it may be possible for the application to establish the session on some request prior to the authentication flow. This should be easy to do when the user first arrives, but it may be harder to guarantee later when the session or auth cookies expire and/or need to be refreshed.
Disable the SessionStateModule: If the application is not relying on session information, but the session module is still setting a cookie that causes the above conflict, then you may consider disabling the session state module.
Reconfigure the CookieAuthenticationMiddleware to write directly to System.Web's cookie collection.
I will opt for the third option, which is to overwrite the default Cookie AuthenticationMiddleware, as they have suggested below.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
public class SystemWebCookieManager : ICookieManager
{
public string GetRequestCookie(IOwinContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
var cookie = webContext.Request.Cookies[key];
return cookie == null ? null : cookie.Value;
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
bool expiresHasValue = options.Expires.HasValue;
var cookie = new HttpCookie(key, value);
if (domainHasValue)
{
cookie.Domain = options.Domain;
}
if (pathHasValue)
{
cookie.Path = options.Path;
}
if (expiresHasValue)
{
cookie.Expires = options.Expires.Value;
}
if (options.Secure)
{
cookie.Secure = true;
}
if (options.HttpOnly)
{
cookie.HttpOnly = true;
}
webContext.Response.AppendCookie(cookie);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
AppendResponseCookie(
context,
key,
string.Empty,
new CookieOptions
{
Path = options.Path,
Domain = options.Domain,
Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
});
}
}
I will give that a crack, and post my results back here.

ASP.net Identity 2 sign in continue to use old password after changing password

I am using the Change Password functionality that visual studio generated for the accountcontroller. I am able to change the password without errors but when I go to login using the new password, I get a login error but if I use the old password, it works.
If I restart the app then the newly changed password takes effect. I am also using Autofac, may be I am not configuring the container correctly.
var builder = new ContainerBuilder();
builder.Register(c => new ApplicationDataContext(connectionString)).InstancePerLifetimeScope();
builder.RegisterType<ApplicationUserManager>().AsSelf();
builder.RegisterType<ApplicationRoleManager>().AsSelf();
builder.Register(c => new UserStore<ApplicationUser>(c.Resolve<ApplicationDataContext>())).AsImplementedInterfaces();
builder.Register(c => new RoleStore<IdentityRole>(c.Resolve<ApplicationDataContext>())).AsImplementedInterfaces();
builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).As<IAuthenticationManager>();
builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("Application​")
});
builder.Register(c => new ApplicationOAuthProvider(publicClientId, c.Resolve<ApplicationUserManager>())).As<IOAuthAuthorizationServerProvider>();
Any help will be much appreciated.
Thanks
--------UPDATED----------
ContanierConfig.cs
public static void Configure(HttpConfiguration config)
{
// Configure the application for OAuth based flow
const string publicClientId = "self";
// ContainerConfig Config
var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
var elasticsearchUrl = ConfigurationManager.AppSettings["ElasticSearchUrl"];
var elasticSearchName = ConfigurationManager.AppSettings["ElasticSearchName"];
var builder = new ContainerBuilder();
builder.Register(c => new BimDataContext(connectionString)).InstancePerRequest();
builder.RegisterType<ApplicationUserManager>().AsSelf().InstancePerRequest();
builder.RegisterType<ApplicationRoleManager>().AsSelf().InstancePerRequest();
builder.Register(c => new UserStore<ApplicationUser>(c.Resolve<BimDataContext>())).AsImplementedInterfaces().InstancePerRequest();
builder.Register(c => new RoleStore<IdentityRole>(c.Resolve<BimDataContext>())).AsImplementedInterfaces().InstancePerRequest();
builder.Register(c => HttpContext.Current.GetOwinContext().Authentication).As<IAuthenticationManager>().InstancePerRequest();
builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager>
{
DataProtectionProvider = new DpapiDataProtectionProvider("Application​")
}).InstancePerRequest(); ;
builder.RegisterType<SimpleRefreshTokenProvider>().As<IAuthenticationTokenProvider>().InstancePerRequest();
builder.RegisterType<AuthRepository>().As<IAuthRepository>().InstancePerRequest();
builder.Register(c => new ApplicationOAuthProvider(
publicClientId,
c.Resolve<ApplicationUserManager>(),
c.Resolve<IAuthRepository>()))
.As<IOAuthAuthorizationServerProvider>().InstancePerRequest();
// Register your Web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
// UoW registration: being explicit
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
// Repositories registration
builder.RegisterAssemblyTypes(typeof(ClientRepository).Assembly)
.AsImplementedInterfaces()
.InstancePerRequest();
// Services registration
builder.RegisterAssemblyTypes(typeof(ClientService).Assembly)
.AsImplementedInterfaces()
.InstancePerRequest();
builder.RegisterAssemblyTypes(typeof(ClientSearchService).Assembly)
.AsImplementedInterfaces()
.InstancePerRequest();
builder.RegisterType<IfcFileImportTask>().As<IIfcFileImportTask>().InstancePerRequest();
builder.RegisterType<COBieFileImportTask>().As<ICOBieFileImportTask>().InstancePerRequest();
// Hangfire registration
builder.RegisterType<BackgroundJobClient>().As<IBackgroundJobClient>().InstancePerRequest();
// OPTIONAL: Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
// Set the dependency resolver to be Autofac.
var container = builder.Build();
JobActivator.Current = new AutofacJobActivator(container);
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
Startup.Auth.Cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure the db context and user manager to use a single instance per request
//app.CreatePerOwinContext(BimDataContext.Create);
//app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
var oAuthAuthorizationServerProvider = GlobalConfiguration.Configuration.DependencyResolver.GetRequestLifetimeScope().Resolve<IOAuthAuthorizationServerProvider>();
var authenticationTokenProvider = GlobalConfiguration.Configuration.DependencyResolver.GetRequestLifetimeScope().Resolve<IAuthenticationTokenProvider>();
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = oAuthAuthorizationServerProvider,
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(5),
RefreshTokenProvider = authenticationTokenProvider,
// In production mode set AllowInsecureHttp = false,
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
Getting error
"value cannot be null. parameter name context autofac" on line var oAuthAuthorizationServerProvider = GlobalConfiguration.Configuration.DependencyResolver.GetRequestLifetimeScope().Resolve<IOAuthAuthorizationServerProvider>();
I was missing a key component of oauth2, the solution to this problem is refresh_tokens. On change password, invalidate the refresh token and force user to log out.
http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
If using ASP.NET (this includes MVC and Web API, Web Forms, etc) and AutoFac you should register all your components using the extension method .InstancePerRequest(). The only exception is for components that are thread safe and where you do not have to worry about errors/unexpected results occurring from one request accessing the same (stale) data as another. An example might be a Factory or a Singleton.
Example of use on a line of code:
builder.Register(c => new UserStore<ApplicationUser>(c.Resolve<ApplicationDataContext>())).AsImplementedInterfaces().InstancePerRequest();
This ensures that every new incoming Http Request will get its own copy of that implementation (resolved and injected hopefully via an interface). Autofac will also cleanup the Disposable instances at the end of each request.
This is the behavior you need. It ensures that there is no cross request interference (like one request manipulating data on a shared dbcontext on another request). It also ensures that data is not stale as it is cleaned up after each request ends.
See the Autofac documentation for more details (here an excerpt).
Instance Per Request
Some application types naturally lend themselves to “request” type semantics, for example ASP.NET web forms and MVC applications. In these application types, it’s helpful to have the ability to have a sort of “singleton per request.”
Instance per request builds on top of instance per matching lifetime scope by providing a well-known lifetime scope tag, a registration convenience method, and integration for common application types. Behind the scenes, though, it’s still just instance per matching lifetime scope.
Changing your DI definitions above to include this should resolve your issues (I think based on what you have provided). If not then it might be a problem with your Identity registration in which case you should post that code so it can be scrutinized.

Extending service stack authentication - populating user session with custom user auth meta data

I am trying to extend Service Stack's authentication and registration features. I have the authentication and registration working fine, however I need to add some custom data for each user. From Service Stack's documentation and various other posts I found you can add your own data using the MetaData column built into the UserAuth table.
I created a CustomAuthRepository so I can set the meta data property of UserAuth, here is my custom repo:
public class CustomAuthRepository : OrmLiteAuthRepository, IUserAuthRepository
{
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new LoginInfo
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
}
This is working great for setting the meta data, I end up with a serialized version of the LoginInfo object in the meta data column of the UserAuth table.
Now what I am trying to do is when a user authenticates I need to change the AuthResponse based on some of that meta data. For example, if a user is not yet activated I want to return an AuthResponse with a property IsActive = get value from custom meta data
I figure I could do this if I can get my custom metadata into the AuthSession. That way in my custom credentials auth provider I could change the response object based on what's in the AuthSession:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var customUserAuthSession = (CustomUserAuthSession)session;
if (!customUserAuthSession.LoginInfo.IsActive)
{
return new
{
UserName = customUserAuthSession.UserName,
IsActive = customUserAuthSession.LoginInfo.IsActive
};
}
var isAuthenticated = base.Authenticate(authService, session, request);
return isAuthenticated;
}
}
Am I going about this the right way, or is there a better way to store and retrieve custom meta data?
How can I change the AuthResponse based on a user's custom meta data?
How can I get my custom meta data into the AuthSession?
Edit
I am getting closer to what I am trying to do. In my CustomAuthSession OnAuthenticated() method :
public override void OnAuthenticated(IServiceBase authService, IAuthSession session, IOAuthTokens tokens, Dictionary<string, string> authInfo)
{
var customUserAuthSession = (CustomUserAuthSession) session;
var userAuth = authService.ResolveService<IUserAuthRepository>().GetUserAuth(session, null);
customUserAuthSession.LoginInfo = userAuth.Get<LoginInfo>();
authService.SaveSession(customUserAuthSession);
base.OnAuthenticated(authService, session, tokens, authInfo);
}
I am refetching the UserAuth and populating the session with the data that I need. Based on the service stack documentation for a custom user session, you need to save the session after you populate it with some custom data. I am doing that but it doesn't seem to be saving.
In my CustomCredentialsAuthProvider, Authenticate method, I don't see the custom data I've added to the session.
Edit
The problem with my first edit above is that the user gets authenticated, then we get to the CustomAuthSession code where I can check if they are active or not. In the case they are not active I would need to log them out, not ideal.
I found instead that I can do all of this in the Authenticate method of my custom CredentialsAuthProvider.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var loginInfo = userAuth.Get<LoginInfo>();
if (!loginInfo.IsActive)
{
return new CustomAuthResponse
{
UserName = userAuth.UserName,
ResponseStatus = new ResponseStatus("500"),
IsActive = loginInfo.IsActive
};
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When the request comes in I can use the username in the request to fetch the UserAuth, and check if the user IsActive or not. If not then I can return some error before Service Stack authenticates them.
I think this works well enough for what I am trying to do. I should be able to return an error to the client saying the user is not active.
If anyone has a cleaner way to do this that would be great.
Here is my answer so far. It works and I can do what I am trying to do, but I would love to hear from some of the Service Stack guys as to whether this is the best way to go about this.
To Save custom meta data
Create a new class that subclasses the OrmLiteAuthRepository. In my case I just want to use Service Stack's built in Sql database persistence and have it create the tables needed.
Re-implement the CreateUserAuth method to save any custom metadata :
public UserAuth CreateUserAuth(UserAuth newUser, string password)
{
newUser.Set(new AccountStatus
{
IsActive = false,
PasswordNeedsReset = true
});
return base.CreateUserAuth(newUser, password);
}
Fetching custom meta data
Create a new class that subclasses the CredentialsAuthProvider. Override the Authenticate method.
public override object Authenticate(IServiceBase authService, IAuthSession session, Auth request)
{
var userAuthRepo = authService.ResolveService<IUserAuthRepository>();
var userAuth = userAuthRepo.GetUserAuthByUserName(request.UserName);
var accountStatus= userAuth.Get<AccountStatus>();
if (!accountStatus.IsActive)
{
throw new InvalidOperationException(string.Format("User {0} is not activated.", request.UserName));
}
if (!accountStatus.PasswordNeedsReset)
{
throw new InvalidOperationException("Your password needs to be reset before you can login.");
}
var authResponse = (AuthResponse)base.Authenticate(authService, session, request);
return authResponse;
}
When an authentication request comes into this method, fetch the UserAuth and the custom meta data. If the user is inactive, or their password needs to be reset throw an InvalidOperationException with an error message.
In my client application's Login controller I can check the error message coming back from the service and redirect the user to some page saying there account isn't active yet, or their password needs to be reset before they can be authenticated.

Resources