Bearer token is not validating with authorize attribute - api

I have a simple api controller with a method "GetDashboard" that contains an Authorize attribute like so...
[RoutePrefix("api/Dashboard")]
public class DashboardController : ApiController
{
[HttpGet]
[Authorize]
[Route("GetDashboard")]
public HttpResponseMessage GetDashboard()
{
//Do stuff...
}
}
I'm using Owin pipeline and bearer tokens for my api authorization and in my Owin configuration i have created two authorization providers using the app.Map() functionality to select the correct mechanism for authorizing users depending your on your entry point to the api like so...
app.Map("/RouteOne", app1 =>
{
appAteb.UseCookieAuthentication(new CookieAuthenticationOptions());
appAteb.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "app1";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Authenticate1"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = false
};
app1.UseOAuthBearerTokens(OAuthOptions);
});
app.Map("/RouteTwo", app2 =>
{
app2.UseCookieAuthentication(new CookieAuthenticationOptions());
app2.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "app2";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Authenticate2"),
Provider = new AnotherAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = false
};
app2.UseOAuthBearerTokens(OAuthOptions);
});
Both mechanisms authenticate correctly, the bearer token is generated and passed back to the browser however when then use the token for authorization on my dashboard controller it returns a 401 Unorthorized?
I suspect its something to do with the app.map because if i remove it and just have one mechanism calls to my dashboard controller work fine however i need to be able to use both mechanisms of authorization and my api controllers to accept the tokens.
Any help on solving this issue would be greatly appreciated.
Thanks

The RoutePrefix of your action GetDashboard() should include the route of one of the maps. For example you could redefine your maps as api/RouteOne and api/RouteTwo and then the route prefix could be for example [RoutePrefix("api/RouteOne/Dashboard")].
I hope it helps.

Related

More than one IDP for SAML2 login with Sustainsys using .NET Core

We are using Sustainsys middleware with .NET Core to connect to an SAML2 IDP. It works well.
However, when we add more than one IDP in the Startup.cs we get in trouble. The user will select which IDP to login to and then the code should send a challenge to that IDP.
How to we specify which IDP in the code?
Using standard .NET Framework it is straight forward:
Context.GetOwinContext().Environment.Add("saml2.idp", new Entity(IDP2EntityId));
but there is no such construct in the .NET Core middleware.
Here is my code. Basically I add two IDPs during startup but I don't know how to specify which one during login/challenge? With this code IDP-1 is always selected because it was the first one added.
STARTUP.CS
public void ConfigureServices(IServiceCollection services)
{
var authenticationBuilder = GetAuthenticationBuilder(services);
string authenticationScheme = "saml2.idp"
authenticationBuilder.AddSaml2(authenticationScheme, options =>
{
options.SPOptions = GetSPOptions();
// Add IDP-1
options.IdentityProviders.Add(
new IdentityProvider(new EntityId(IDPEntityUrl1), options.SPOptions)
{
MetadataLocation = IDPMetadataUrl1
});
// Add IDP-2
options.IdentityProviders.Add(
new IdentityProvider(new EntityId(IDPEntityUrl2), options.SPOptions)
{
MetadataLocation = IDPMetadataUrl2
});
}
}
LOGINCONTROLLER.CS
string saml2AuthenticationScheme = "saml2.idp";
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", saml2AuthenticationScheme } }
};
return Challenge(properties: props, saml2AuthenticationScheme);
How do I specify which IDP to use in the LoginController?
I found the solution. We studied the Sustainsys code and found the undocumented (?) feature to specify the IDP in the AuthenticationProperties.Items with an "idp" item. Like this:
LoginController.cs
string saml2AuthenticationScheme = "saml2.idp";
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", saml2AuthenticationScheme }, { "idp", theSelectedIDPIdentityId } }
};
return Challenge(properties: props, saml2AuthenticationScheme);

Sustainsys Saml2 Handler AuthenticateAsync() method operation is not implemented

I'm trying a simple implementation in my Asp net Core application of Saml2 to integrate with an Ad FS server. I can't figure why I am getting this error. I downloaded the samples from the gitHub and tried to adapt it in my application.
NotImplementedException: The method or operation is not implemented.
Sustainsys.Saml2.AspNetCore2.Saml2Handler.AuthenticateAsync()
Here's my implementation, my application is running on Asp Net Core
On StartUp
services
.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = Saml2Defaults.Scheme;
})
.AddSaml2(options =>
{
options.SPOptions.EntityId = new EntityId("http://myAdfsServer.myDomain.com/adfs/services/trust");
options.SPOptions.ReturnUrl = new Uri("https://localhost:5000");
options.IdentityProviders.Add(
new IdentityProvider(new EntityId("http://myAdfsServer.myDomain.com/adfs/services/trust"), options.SPOptions)
{
LoadMetadata = true,
MetadataLocation = "https://myAdfsServer.myDomain.com/FederationMetadata/2007-06/FederationMetadata.xml"
//MetadataLocation = "FederationMetadata.xml"
});
//options.SPOptions.ServiceCertificates.Add(new X509Certificate2(certificate.ToString()));
})
.AddCookie();
On my Controller
trying something similar to Sustainsys SAML2 Sample for ASP.NET Core WebAPI without Identity
[Authorize(AuthenticationSchemes = Saml2Defaults.Scheme)]
public class AuthenticationController : Controller
{
public AuthenticationController()
{
}
[AllowAnonymous]
public async Task LoginAdfs()
{
string redirectUri = string.Concat("https://localhost:5000", "/verifyAdfs");
try
{
new ChallengeResult(
Saml2Defaults.Scheme,
new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(LoginCallback), new { redirectUri })
});
}catch(Exception e)
{
}
}
[AllowAnonymous]
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync(Saml2Defaults.Scheme);
//_log.Information("Authenticate result: {#authenticateResult}", authenticateResult);
// I get false here and no information on claims etc.
if (!authenticateResult.Succeeded)
{
return Unauthorized();
}
var claimsIdentity = new ClaimsIdentity("Email");
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
// _log.Information("Logged in user with following claims: {#Claims}", authenticateResult.Principal.Claims);
await HttpContext.SignInAsync("Email", new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
}
note: I've got a client that won't expose his MetaData in a URL, so I'll need to adapt it and set manually the metadata parameters
I'm stuck in this error, I does not even hit my method LoginAdfs.
The Saml2 handler cannot be used as an authencation scheme, it is a challenge scheme.
I guess that the LoginAdfs() method works fine, but that it's the LoginCallback that fails. The reason should be the call to HttpContext.AuthenticationAsync(Saml2Defaults.Scheme).
You should instead authenticate with the cookie scheme - because that's what keeps the session. Internally when the challenge is completed, the Saml2 handler will use the DefaultSignInScheme to preserve the result in a session (through a cookie, as that's the default sign in scheme).

Is there possible to use Cookie and Token base authentication in same app in asp.net core app 2.1?

I have a case when I need to use 2 type of auth in one Web app:
for MVC part of app - Cookie base auth, and for Angular part JWT Token base auth.
I would like to use configuration approach over annotation.
In the setup.cs I would like to configure: all controlles methods that have /api/* in the URL path to use JWT token base auth and all controlles methods that haven't /api/* to use Cookie.
To implement this, I use two policies in the combination with custom AuthorizeFilter:
var jwtPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
var cookiePolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new CustomAuthorizeFilter(jwtPolicy, applyOnPath: "/api"));
options.Filters.Add(new CustomAuthorizeFilter(cookiePolicy, notApplyOnPath: "/api"));
Ande here is implementation of CustomAuthorizeFilter:
public class CustomAuthorizeFilter : AuthorizeFilter
{
private string _applyOnPath;
private string _notApplyOnPath;
public CustomAuthorizeFilter(AuthorizationPolicy policy, string applyOnPath = null, string notApplyOnPath = null) : base(policy)
{
_applyOnPath = applyOnPath;
_notApplyOnPath = notApplyOnPath;
}
public override Task OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context)
{
if (_applyOnPath != null && context.HttpContext.Request.Path.StartsWithSegments(_applyOnPath) == true)
return base.OnAuthorizationAsync(context);
else if (_notApplyOnPath != null && context.HttpContext.Request.Path.StartsWithSegments(_notApplyOnPath) == false)
return base.OnAuthorizationAsync(context);
else //Otherwise apply this policy
return Task.FromResult(0);
}
}
It was work fine in the .netCore 2.0, but after upgrade to .netCore 2.1.* it stop working...
Does anybody have a idea whet is the problem?

How can MonoTouch supply cookie on each ServiceStack request?

I've spent several days attempting to get to grips with ServiceStack and it seems great. Only issue is with authentication which seems to be a lot of friction, hard work and tears.
I want MonoTouch to register users, authenticate against ServiceStack/authenticate against OAuth and generally minimise calls to the database when authenticating.
So far I have got this:
var client = new JsonServiceClient(newbaseUri);
// register a new user:
var registration = new Registration {
FirstName = "john"
UserName = "user" ,
Password = "pass",
Email = "john#john.com",
};
var registerResponse = client.Send<RegistrationResponse>(registration);
--------------------------------
// user registered...later on I authenticate:
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
});
var authResponse = clientlogin.Send<AuthResponse>(auth);
--------------------------------
// somehow I need to store 'authresponse' for later calls, not sure how without a browser
// tried manually setting the cookies and credentials parameters later but no joy
// but now I need to call a secured ([Authenticate] decorated) service method:
var client = new JsonServiceClient(newbaseUri);
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
return response.Result;
-----------------------------------------
// heres the configuration
var appSettings = new AppSettings();
//Default route: /auth/{provider}
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(appSettings), // never seems to get called
//new FacebookAuthProvider(appSettings), // not sure how to get this to work on monotouch
//new TwitterAuthProvider(appSettings), // same issue as facebook
new BasicAuthProvider(appSettings) // works but what about caching/tokens/cookies?
}));
//Default route: /register
Plugins.Add(new RegistrationFeature()); // how do i send extra params to this as created in mongodb collection
var mongoClient = new MongoClient("mongodb://localhost");
var server = mongoClient.GetServer();
var db = server.GetDatabase("users");
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<IUserAuthRepository>(new MongoDBAuthRepository(db, true));
My question is:
1) How do I enable extra fields to be passed in along with registration (as the mongodb [Servicestack.Authentication.Mongodb] has lots of empty fields i.e. birthdate, firstline, city, timezone, etc) that are not present in ServiceStack.Common.ServiceClient.Web.Registration object?
2) How can I transfer the cookie (or even maybe a token system) sent in the 'authresponse' to subsequent calls in order to allow ServiceStack to match against the session for ongoing authentication rather than more ongoing database calls that what seems to be issue with 'basic authentication' method (i.e CredentialsAuthProvider doesnt get called on server side)?
Please help...I've read documentation, run tests, examined social bootstrap and now I'm seriously losing days over this and thinking of integrating SS with simplemembership or even throwing ServiceStack away completely for old skool soap/wcf which is far easier to implement by the looks of it :(
1) If you want to use the Registration Plugin I don't think you can add additional fields since the Registration request/class is already defined. You could make your own registration Service and call into the RegistrationService/Plugin. Also, this post might be helpful.
[Route("/myregistration")]
public class MyRegistration : Registration //Add Additional fields for registration
{
public DateTime? BirthDate { get; set; }
public string Gender { get; set; }
}
public class MyRegisterService : Service
{
public IUserAuthRepository UserAuthRepo { get; set; }
public object Post(MyRegistration request)
{
using (var registrationService = base.ResolveService<RegistrationService>())
{
//handle the registration
var response = registrationService.Post(request.TranslateTo<Registration>());
}
//save the additional data
var userAuth = request.TranslateTo<UserAuth>();
UserAuthRepo.SaveUserAuth(userAuth);
//can make your own response or grab response from RegistrationService above
return new MyRegistrationResponse();
}
}
2) You can authenticate your JsonServiceClient and reuse it to make multiple requests.
var client = new JsonServiceClient(newbaseUri);
var authResponse = client.Send<AuthResponse>(new Auth {
UserName = "user",
Password = "pass",
RememberMe = true
}); //if successful your 'client' will have a populated CookieContainer with 'ss-id' and 'ss-pid' values
//reusing 'client' (after successful authentication) to make a request
//to a service requiring authentication
var response = client.Send<HelloResponse>(new Hello { Name = "World!" });
If reusing your 'client' is not an option you can try to store the ss-id. I don't know much about MonoTouch and how it stores 'browser sessions' so I'm not sure how you would accomplish this. After you authenticate and store the ss-id you can add it to the client using a Request Filter
//Get ss-id value
foreach(Cookie cookie in previousAuthenticatedClient.GetCookies(new Uri(newbaseUri)))
{
if (cookie.Name == "ss-id")
{
//store ss-id
}
}
var newClient = new JsonServiceClient(newbaseUri)
{
LocalHttpWebRequestFilter = (req) =>
{
req.CookieContainer.Add(new Uri("http://localhost:56006"), new System.Net.Cookie("ss-id", ssId));
}
};

Authenticate SignalR Hub using ServiceStack Authentication Plugin

I have created a ServiceStack service on top of Asp.Net that implements Basic authentication. Everything is working fine on the service routes. I am able to login and I get the session cookies which are validated on subsequent calls. I'm using an HttpClient for those requests.
I also have a SignalR Hub that runs on the same Asp.Net service, but the Principal is not authenticated on my Hub methods.
Basically what I need is for ServiceStack to intercept calls into my Hub and validate the session cookie and populate the Context.User.Identity and mark it as authenticated. If I can get that set up, a simple [Authorize] attribute on my hub will do the rest.
Here is a sample of my code:
// set up a HttpClient with a cookie container to hold the session cookie
var cookieJar = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cookieJar, UseCookies = true, UseDefaultCredentials = false };
var client = new HttpClient(handler) { BaseAddress = _baseUri };
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password))));
// do client login and get response with session cookie...
var response = client.PostAsync(...);
// add the cookies to the SignalR hub connection
var responseCookies = cookieJar.GetCookies(_baseUri);
var cookieContainer = new CookieContainer();
foreach (Cookie cookie in responseCookies)
{
cookieContainer.Add(cookie);
}
_hubConnection = new HubConnection(_baseUri.ToString()) { CookieContainer = cookieContainer };
After this setup, my session cookies are sent to the Hub on each invocation. Somehow I need for ServiceStack to intercept those requests and set the authenticated user.
Let ServiceStack do the authenication and persisting the user session. Then in the SignalR hub endpoints that need authentication put this code:
var cache = AppHostBase.Resolve<ICacheClient>();
var sess = cache.SessionAs<AuthUserSession>();
if (!sess.IsAuthenticated)
throw new AuthenticationException();
Johan's answer works but it is not very convenient to put this code to every method and if you put it in the hub constructor, it will fail in the web on page refresh with "Only ASP.NET Requests accessible via Singletons are supported" exception.
Instead, I have chosen to create a custom attribute that gives you more control on the hub and method call authorization.
Simplest attribute would look like this:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AuthorizeServiceStack : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return CheckAuthorization();
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return CheckAuthorization();
}
private static bool CheckAuthorization()
{
var cache = AppHostBase.Resolve<ICacheClient>();
var sess = cache.SessionAs<AuthUserSession>();
return sess.IsAuthenticated;
}
}
As you can see, the code is the same as in Johan's answer but it will work in the web as well, ensuring that HttpContext.Current is not null when you are calling cache.SessionAs