Service Stack - Custom authentication on one route - authentication

In my current application, I am using Service Stack with JWT's for security. Security has been implemented and works perfectly. Trouble is, I would like to secure one route differently from the others. There is a document the logged in user retrieves, I want to make sure the document they are retrieving is theirs and not someone else's. It is very sensitive data. I would like to secure it differently because something like PostMan could be used with a valid token to retrieve any document, I want to prevent this. The users id is in the token, I would like to match it against the document that is being retrieved if possible. The current security is implemented like so:
public class AppHost: AppHostBase
{
public override void Configure(Funq.Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JsonWebTokenAuthProvider("myKey", "myAudience"),
}));
}
}
JsonWebTokenAuthProvider is a custom class where security was implemented, this all works perfectly. Here is the code:
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
// first validate the token, then get roles from session
string header = request.oauth_token;
// if no auth header, 401
if (string.IsNullOrEmpty(header))
{
throw HttpError.Unauthorized(MissingAuthHeader);
}
string[] headerData = header.Split(' ');
// if header is missing bearer portion, 401
if (!string.Equals(headerData[0], "BEARER", StringComparison.OrdinalIgnoreCase))
{
throw HttpError.Unauthorized(InvalidAuthHeader);
}
// swap - and _ with their Base64 string equivalents
string secret = SymmetricKey.Replace('-', '+').Replace('_', '/');
string token = headerData[1].Replace("\"", "");
// set current principal to the validated token principal
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(token, secret, Audience, true, Issuer);
string lanId = GetLanID(Thread.CurrentPrincipal.Identity.Name);
string proxyAsLanId = request.Meta.ContainsKey(META_PROXYID) ? request.Meta[META_PROXYID] : null;
if (HttpContext.Current != null)
{
// set the current request's user the the decoded principal
HttpContext.Current.User = Thread.CurrentPrincipal;
}
// set the session's username to the logged in user
session.UserName = Thread.CurrentPrincipal.Identity.Name;
session.Roles = GetApplicableRoles(lanId, proxyAsLanId);
authService.Request.SetItem("lanID", lanId);
authService.Request.SetItem("proxyAsLanId", proxyAsLanId);
return OnAuthenticated(authService, session, null, null);
}
I looked up RequestFilterAttribute found here, but I do not think that is what I want. Ideally, if the check fails I would like to return a 401 (unauthorized) if possible.
What is the best way to do this?

If you just want to handle one route differently than you can just add the validation in your single Service, e.g:
public object Any(MyRequest dto)
{
var lanId = base.Request.GetItem("lanId");
if (!MyIsValid(lanId))
throw HttpError.Unauthorized("Custom Auth Validation failed");
}
You could do the same in a RequestFilter, e.g:
public class CustomAuthValidationAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object responseDto)
{
var lanId = req.GetItem("lanId");
if (!MyIsValid(lanId))
{
res.StatusCode = (int) HttpStatusCode.Unauthorized;
res.StatusDescription = "Custom Auth Validation failed";
res.EndRequest();
}
}
}
And apply it to a single Service:
[CustomAuthValidation]
public object Any(MyRequest dto)
{
//...
}
Or a collection of Services, e.g:
[CustomAuthValidation]
public class MyAuthServices : Service
{
public object Any(MyRequest1 dto)
{
//...
}
public object Any(MyRequest2 dto)
{
//...
}
}

Related

"retrieveUser returned null - a violation of the interface contract" in springboot security when authentication

I am new to springboot security and i am trying write the signup function.My approach is to save the user and then pass the data to the autheicationmanager,but the went in here and it return null and the above error occur.
token service:
public String signup(JpaUser jpaUser) {
System.out.println(jpaUserrepository.findByUsername(jpaUser.getUsername()));
if(jpaUserrepository.findByUsername(jpaUser.getUsername()).isPresent())
{return "repeated username";}
JpaUser saveuser=jpaUserrepository.save(new JpaUser(jpaUser.getUsername(),
passwordEncoder.encode(jpaUser.getPassword()),jpaUser.getEmail(),jpaUser.getRoles()));
Authentication authentication= authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
saveuser.getUsername(),saveuser.getPassword()));
System.out.println(authentication);
String token=generateToken(authentication);
System.out.println(token);
return token;
}
securityconfig:
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) {
try{System.out.println(authConfig.getAuthenticationManager());
return authConfig.getAuthenticationManager();}catch(Exception exception){
System.out.println(exception);
return null;
}
}
generate token method:
public String generateToken (Authentication authentication){
Instant now=Instant.now();
String scope=authentication.getAuthorities().stream() //Stream<capture of ? extends GrantedAuthority >
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer(authentication.getName())
.issuedAt(now)
.expiresAt(now.plus(1, ChronoUnit.HOURS))
.claim("scope",scope).build();
return this.encoder.encode(JwtEncoderParameters.from((claims))).getTokenValue();
}

How to allow multiple roles to access route through RouteClaimsRequirement

In a regular type scenario, where a Route is available, say to only "Premium" users, ocelot.global.json would have RouteClaimsRequirement like this:
"RouteClaimsRequirement" : { "Role" : "Premium" }
This would get translated to a KeyValuePair<string, string>(), and it works nicely.
However, if I were to open a route to 2 types of users, eg. "Regular" and "Premium", how exactly could I achieve this?
I found a way through overriding of default Ocelot middleware. Here are some useful code snippets:
First, override the default AuthorizationMiddleware in Configuration() in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var config = new OcelotPipelineConfiguration
{
AuthorisationMiddleware
= async (downStreamContext, next) =>
await OcelotJwtMiddleware.CreateAuthorizationFilter(downStreamContext, next)
};
app.UseOcelot(config).Wait();
}
As you can see, I am using a custom OcelotJwtMiddleware class up there. Here is that class, pasted:
public static class OcelotJwtMiddleware
{
private static readonly string RoleSeparator = ",";
public static Func<DownstreamContext, Func<Task>, Task> CreateAuthorizationFilter
=> async (downStreamContext, next) =>
{
HttpContext httpContext = downStreamContext.HttpContext;
var token = httpContext.Request.Cookies[JwtManager.AuthorizationTokenKey];
if (token != null && AuthorizeIfValidToken(downStreamContext, token))
{
await next.Invoke();
}
else
{
downStreamContext.DownstreamResponse =
new DownstreamResponse(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
};
private static bool AuthorizeIfValidToken(DownstreamContext downStreamContext, string jwtToken)
{
IIdentityProvider decodedObject = new JwtManager().Decode<UserToken>(jwtToken);
if (decodedObject != null)
{
return downStreamContext.DownstreamReRoute.RouteClaimsRequirement["Role"]
?.Split(RoleSeparator)
.FirstOrDefault(role => role.Trim() == decodedObject.GetRole()) != default;
}
return false;
}
}
JwtManager class here is just my small utility made using the default Jwt NuGet package, nothing special. Also, JWT is being stored as a Cookie, which is not safe, but doesn't matter here. If you happen to copy paste your code, you will have small errors relating to this, but just switch it out with your own implementations of auth tokens.
After these 2 snippets were implemented, ocelot.global.json can have RouteClaimsRequirement such as this:
"RouteClaimsRequirement" : { "Role" : "Premium, Regular" }
This will recognize both clients with Regular in their Cookies, as well as those with Premium.

Spring AOP doesn't always intercept a method

I have a user service. The service has the ability to reset the password.
#Service
public final class UserService {
private final UserMapper userMapper;
#Autowired
public UserService(final UserMapper userMapper) {
this.userMapper = userMapper;
}
#Transactional
public String restorePassword(final String loginOrEmail) throws IllegalArgumentException {
User user = userMapper.findByUsername(loginOrEmail);
if (user == null) {
user = userMapper.findByEmail(loginOrEmail);
if (user == null) throw new IllegalArgumentException("User not found");
}
final String newPassword = PasswordGenerator.generate(2, 2, 2, 4);
returnPasswordAfterRestore(newPassword, user);
//Later, the password will salt and be encrypted before entering the database.
userMapper.setPassword(newPassword, user.getUserId());
return user.getEmail();
}
public void returnPasswordAfterRestore(final String password, final User user) {
System.out.println("------------------------Method run!------------------------");
}
I need to get the generated password and send it to the user. For this I use Spring AOP.
#Before("execution(* com.example.aop.service.UserService.returnPasswordAfterRestore(..))&&args(password, user)")
public void beforeReturnPasswordAfterRestore(String password, User user) {
System.out.println("-------------------------------" + password);
System.out.println("-------------------------------" + user.getUsername() + " mail:" + user.getEmail());
}
When I make an explicit call to the returnPasswordAfterRestore () method, the aspect fulfills correctly and intercepts the parameters, this confirms the debug mode.
userService.returnPasswordAfterRestore("newPass", user);
But when I make a call to the restorePassword () method, which contains a call to the returnPasswordAfterRestore () method, the aspect does not work.
userService.restorePassword(user.getUsername());
How do I solve this problem? Or how can I get the generated password out of a method without saving it to an external variable?

Check if a user has permission to access a controller action (before accessing it)

I have an action, TopSecret(), which has a security policy applied to it:
[Authorize(Policy = "Level2SecurityClearance")]
public IActionResult TopSecret()
I could check the user meets the requirements of the policy by doing this (authorizationService is of type IAuthorizationService)
bool isAuthorised = await authorizationService.AuthorizeAsync(User, "Level2SecurityClearance");
This action may have a different policy applied at some point in the future and I don't want to have to find all the places I generate links to it and update the code. Is it possible to test if a user can access a specific action?
Maybe something like this:
// Not a real method!!!
bool isAuthorised = authorizationService.IsAuthorisedForAction(User, "TopSecret", "SecretController");
You should look into developing Requirements
Here's an example for you using your criteria:
note: I'm assuming you're using Identity3 and your User has claims with the access
In a new class called Level2SecurityClearanceRequirement
public class Level2SecurityClearanceRequirement : AuthorizationHandler<Level2SecurityClearanceRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, Level2SecurityClearanceRequirement requirement)
{
if (context.User.HasClaim("TopSecret","yes")
context.Succeed(requirement);
return Task.FromResult(0);
}
}
In your controller method:
public async Task<IActionResult> BlahBlah() {
if (!await _authorizationService.AuthorizeAsync(User, nameof(PolicyName.Level2SecurityClearance), new Level2SecurityClearanceRequirement()))
return new ChallengeResult();
}
note that I'm using nameof() here so that you don't have any magic strings and all your resources are centralized.
In this case I have an enum:
public enum PolicyName {
Level2SecurityClearance
}
in your startup.cs:
in the ConfigureServices method
add the following:
services.AddAuthorization(options =>{
options.AddPolicy(nameof(PolicyName.Level2SecurityClearance), policy => { policy.AddRequirements(new Level2SecurityClearanceRequirement()); });
});
you can then use this requirement whereever you please and the checks are done in the requirement itself
Try this. Tested in ASP.NET Core 1.1
//somewhere in view
#if (await Url.HasAccess(urlActionContext))
{
<p>You have access</p>
}
Extension method
public static async Task<bool> HasAccess(this IUrlHelper urlHelper, UrlActionContext urlActionContext, string httpMethod = "GET" )
{
var httpContext = urlHelper.ActionContext.HttpContext;
var routeValues = new RouteValueDictionary(urlActionContext.Values);
routeValues["action"] = urlActionContext.Action;
routeValues["controller"] = urlActionContext.Controller;
var path = urlHelper.Action(urlActionContext);
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(new HttpRequestFeature()
{
Method = httpMethod,
Path = path,
});
var ctx = new DefaultHttpContext(features);
var routeContext = new RouteContext(ctx);
foreach (var entry in routeValues)
{
routeContext.RouteData.Values.Add(entry.Key, entry.Value);
}
var actionSelector = httpContext.RequestServices.GetRequiredService<IActionSelector>();
var provider = httpContext.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
var actionDescriptors = actionSelector.SelectCandidates(routeContext);
var actionDescriptor = actionSelector.SelectBestCandidate(routeContext, actionDescriptors);
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
//You need to implement your own AuthorizationHandler that
//checks the actionDescriptor. It will be in AuthorizationHandlerContext.Resource.
//In my case, I have custom Authorize attribute applied to the
//controller action and this attribute is available
//in actionDescriptor.FilterDescriptors
var ok = await authService.AuthorizeAsync(httpContext.User, actionDescriptor, "YOUR_POLICY");
return ok;
}

Custom OpenIdClient for Customer URL in MVC 4

I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];