I am using a 3rd party library names telogis map in my project. For one of its functionality called Clustering, it is not possible to send request header. Only query string can be passed for clustering and entire logic of API call is done within the JS library.
My project use Bearer token based authenticate and built with Web API 2. To resolve this issue I have passed access token in query string and want validate the request. I created below CustomAuthorize attribute for this:
public class ClusterRequestAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
base.OnAuthorization(actionContext);
}
public override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
string accessToken = actionContext.Request.GetQueryNameValuePairs().Where(w => w.Key == "access_token").Select(w => w.Value).DefaultIfEmpty().FirstOrDefault();
actionContext.Request.Headers.Remove("Authorization");
actionContext.Request.Headers.Add("Authorization", accessToken);
actionContext.ControllerContext.Request.Headers.Remove("Authorization");
actionContext.ControllerContext.Request.Headers.Add("Authorization", accessToken);
HttpContext.Current.Request.Headers.Remove("Authorization");
HttpContext.Current.Request.Headers.Add("Authorization", accessToken);
return base.OnAuthorizationAsync(actionContext, cancellationToken);
}
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return base.IsAuthorized(actionContext);
}
}
But IsAuthorized is always returning false. I reviewed the Authorize API internal function using Git Link
According to it, I have to set actionContext.ControllerContext.RequestContext.Header which is not accessible due to protection level as it is marked as internal.
Is there any other work around for this issue or can it be done in better way?
Related
Below code return Mono from ReactiveSecurityContextHolder
#Component
public class SomeClass {
public static Mono<String> issuesID() {
return ReactiveSecurityContextHolder.getContext().switchIfEmpty(Mono.empty()).flatMap((securityContext) -> {
Authentication authentication = securityContext.getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();
String issuer = (String) jwt.getClaims().get("some_claim");
log.info("{}",issuer);
return Mono.justOrEmpty(issuer);
}).switchIfEmpty(Mono.empty());
}
}
I need something like this
String mutatedId = PREXIX+SomeClass.issuesID();
If i do followings,
PREXIX+SomeClass.issuesID().block();
PREXIX+SomeClass.issuesID().subscribeOn(Schedulers.elastic()).block();
PREXIX+SomeClass.issuesID().toProcessor().block();
PREXIX+SomeClass.issuesID().toFuture().get();
They all give the same error.
block()/blockFirst()/blockLast() are blocking, which is not supported in thread rector-xxx
I have also tried delaying the Mono but that's also not helping.
I know reactor is a non-blocking framework and blocking is discourages but in my case I can't figure out another way to solve this.
I need to pass the mutatedId to mongoFactory so i can switch databases based on Jwt property per request. I do not want to inject #AuthenticationPrincipal in controller and keep passing it to downward layers and finally make decision at DOA layer.
#Override
public Mono<MongoDatabase> getMongoDatabase(String dbName) throws DataAccessException {
String mutatedId = PREXIX+SomeClass.issuesID();
return super.getMongoDatabase(mutatedId+"#"+dbName);
}
Any suggestion how this can be achieved or is there any better approach.
You use for instance flatMap.
Mono<MongoDatabase> db = issuesID()
.flatMap(id -> getMongoDatabase(id));
I have set up authentication in a Spring WebFlux application. The authentication mechanism appears to work fine. For example the following code is used to set up security web filter chain:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/path/to/resource").hasAuthority("A_ROLE")
.anyExchange().authenticated()
.and().httpBasic()
.and().build();
}
This works as expected in conjunction with the UserDetailsRepositoryReactiveAuthenticationManager and MapReactiveUserDetailsService. If a user doesn't have the required authority a forbidden error code is returned and otherwise the request is passed on to the handler.
I have a requirement to apply fine grained permission checks within the handler itself and figured that I should be able to retrieve the authorities from the request as follows:
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
Authentication authentication = (Authentication)serverRequest.principal().block();
...
}
However, I find that the principal is always null. First, is this the correct way to get a handle on the authorities, and if so is there possibly some upstream configuration I'm missing?
You are blocking the result before is available. You can simply flatmap it so that you don't have to block it.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return serverRequest.principal().flatMap((principal) -> ServerResponse.ok()
.body(fromObject("Hello " + principal.getName())));
}
UPDATE: If you want to retrieve the principal and body you could zip them.
public Mono<ServerResponse> getMyResource(ServerRequest serverRequest) {
return Mono.zip(
serverRequest.principal(),
serverRequest.bodyToMono(String.class)
).flatMap(tuple -> {
Principal principal = tuple.getT1();
String body = tuple.getT2();
return ServerResponse.ok().build();
});
}
Here's what I currently have:
Spring REST service where many of the APIs require the user to be authenticated
A 'registration' API (/api/v1/register)
A 'login' API that takes username/password (/api/v1/login)
'Facebook Login' API that relies on Spring Social and Spring Security to create a User Connection and log my user in (/auth/facebook)
My problem is that I want these APIs to be used by multiple clients, but the way Facebook Login is right now, it doesn't work well on mobile (works great on a website).
Here's the mobile scenario:
I use Facebook's iOS SDK to request permission from the user
Facebook returns a user access token
I want to send my backend service this token and have Spring Social accept it, create the User Connection, etc.
Can this be done? Or am I going to have to write my own API to persist the User Connection?
Appreciate any help!
I had the exact same issue and here's how I made it work. You probably have a SocialConfigurer somewhere with the following:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
#Autowired
private DataSource dataSource;
#Bean
public FacebookConnectionFactory facebookConnectionFactory() {
FacebookConnectionFactory facebookConnectionFactory = new FacebookConnectionFactory("AppID", "AppSecret");
facebookConnectionFactory.setScope("email");
return facebookConnectionFactory;
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(facebookConnectionFactory());
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
// Other #Bean maybe ...
}
From here, what you can do is, in a Controller/RestController, add a mapping with a RequestParam for your token that you will send to your server:
#Autowired
private FacebookConnectionFactory facebookConnectionFactory;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#RequestMapping(value = "/my-facebook-url", method = RequestMethod.POST)
public String fb(#RequestParam String token) {
AccessGrant accessGrant = new AccessGrant(token);
Connection<Facebook> connection = facebookConnectionFactory.createConnection(accessGrant);
UserProfile userProfile = connection.fetchUserProfile();
usersConnectionRepository.createConnectionRepository(userProfile.getEmail()).addConnection(connection);
// ...
return "Done";
}
Useful references
UsersConnectionRepository
ConnectionRepository
I create a custom BasicAuthenticationMiddleware that use a BasicAuthenticationHandler to Authenticate requests from client to WebAPI.
The BasicAuthenticationHandler derives from the AuthenticationHandler< TOptions > base class.
Everything works fine and I implemented the
AuthenticateCoreAsync
where the logic to authenticate happens
ApplyChallengeResponseAsync
where the logic, in case of not authenticated requests, sends the WWW-Authenticate header to the client.
What I would like to achieve now is to set a Custom Body in the Response (IOwinResponse, inside the ApplyChallengeResponseAsync, with a custom object like:
{
Code="999",
Description="My failing reason"
AdditionalInfo = "My additional infos"
}
instead of the standard message that is like
{
message="Authorization has been denied for this request."
}
Did you have any suggestion on this?
thanks
The standard message you see, which is "Authorization has been denied for this request." is created by the Authorize filter. The HandleUnauthorizedRequest method sets this message in the response.
protected virtual void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
actionContext.Response = actionContext.ControllerContext.Request
.CreateErrorResponse(
HttpStatusCode.Unauthorized,
SRResources.RequestNotAuthorized);
}
SRResources.RequestNotAuthorized is what you see as the standard message.
Now, ApplyChallengeResponseAsync is called from the OnSendingHeaders callback in Katana autentication micro framework. This callback is invoked when a component writes into the response stream. In our case, when the response message created by the filter (what you see above) gets serialized, that is when the callback is invoked and ApplyChallengeResponseAsync runs. By that time, it is already too late for you to change the response. The best bet will be to override the virtual method of the Authorize filter above like this.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse<MyError>
(new MyError() { Description = "My failing reason" });
response.StatusCode = HttpStatusCode.Unauthorized;
actionContext.Response = response;
}
}
public class MyError
{
public string Description { get; set; }
}
Instead of using [Authorize] on the controller or action method, use [MyAuthorize].
Environment: ASP.NET MVC 4, Visual Studio 2012
The [Authorize] attribute verifies that the user has a valid login cookie, but it does NOT verify that the user actually exists. This would happen if a user is deleted while that user's computer still holds the persisted credentials cookie. In this scenario, a logged-in non-user is allowed to run a controller action marked with the [Authorize] attribute.
The solution would seem to be pretty simple: Extend AuthorizeAttribute and, in the AuthorizeCore routine, verify that the user exists.
Before I write this code for my own use, I'd like to know if someone knows of a ready-to-go solution to this gaping hole in the [Authorize] attribute.
You need a special authentication global action filter.
Solution to your problem is the following. You have to introduce the global action filter that will be executed before controller action is invoked. This event is named OnActionExecuting. And within this global action filter you can also handle the scenario that user have a valid auth cookie, but does not exists in persistence (DB) anymore (and you have to remove its cookie).
Here is the code example to get an idea:
public class LoadCustomPrincipalAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CustomIdentity customIdentity;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
UserData userData = UserRepository.GetUserByName(HttpContext.Current.User.Identity.Name);
if (userData == null)
{
//TODO: Add here user missing logic,
//throw an exception, override with the custom identity with "false" -
//this boolean means that it have IsAuthenticated on false, but you
//have to override this in CustomIdentity!
//Of course - at this point you also remove the user cookie from response!
}
customIdentity = new CustomIdentity(userData, true);
}
else
{
customIdentity = new CustomIdentity(new UserData {Username = "Anonymous"}, false);
}
HttpContext.Current.User = new CustomPrincipal(customIdentity);
base.OnActionExecuting(filterContext);
}
}
Hope it helps to you!
Do not forget to register this action filter as a global one. You can do this like:
private static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LoadCustomPrincipalAttribute());
}
Just to add this. Leave alone AuthorizeAttribute. It should work as it was meant. It simply check the HttpContext.Current.User.Identity.IsAuthenticated == true condition. There are situations that you would need to overide it, but this is not the one. You really need a proper user/auth handling before even AuthorizeAttribute kicks in.
Agreed with Peter. Here is what I did for an AngularJs app. Create an attribute that checks the lockout date. Change YourAppUserManager out with the correct one.
public class LockoutPolicyAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var now = DateTime.UtcNow;
var currentUserId = Convert.ToInt32(HttpContext.Current.User?.Identity?.GetUserId());
var user = await HttpContext.Current.GetOwinContext().GetUserManager<YourAppUserManager>().FindByIdAsync(currentUserId);
if (user?.LockedOutUntil >= now)
{
actionContext.Response = actionContext.Request.CreateErrorResponse((HttpStatusCode)423, "Account Lockout");
return;
}
}
base.OnActionExecuting(actionContext);
}
}
Then have an AngularJs intercept service for status code 423 to redirect to login page.
switch (response.status) {
case 423: //Account lockout sent by the server.
AuthService.logOut();
window.location.href = '/login';