I have a setup as described in the title consisting of a default policy containing a requirement that can be fulfilled through a respective handler:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authorizationContext, AccessRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext;
var identity = authorizationContext.User.Identity;
if (!identity.IsAuthenticated)
{
var result = await httpContext.AuthenticateAsync({"Negotiate || Cookie"});
if (!result.Succeeded)
{
return;
}
}
authorizationContext.Succeed(requirement);
}
As soon as I do not invoke these handlers through the default policy, the site will be displaying an simple 401 even if Negotiate authentication could be resolved. A way to solve this without handlers might actually solve the subsequent issue as well.
If i prioritize Negotiate (through explicitly setting default scheme or by registering it first) the user will be prompted for his Active Directory credentials if Negotiate is unavailable, while it should instead fall back to Cookie authentication.
However, if I prioritize Cookie authentication, the user will be prompted for his Cookie dedicated credentials even if Negotiate could work.
Is there any configuration that allows for some sort of try-catch mechanism to attempt Negotiate and immediately fall back to another scheme if it is unresolvable?
I tampered by setting different DefaultChallengeScheme parameters but was unsuccessful this far.
For the problem of 401: when u add service in startup:services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme). Here "JwtBearerDefaults.AuthenticationScheme" is the name of the scheme to use by default when a specific scheme isn't requested. However, when an authentication challenge is invoked by Authorization, a JWT bearer scheme returning a 401 result with a www-authenticate: bearer header.
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0
If u wanna use multiple authentication methods u can try "AuthenticationSchemeOptions Class", inside the class has properties can be set.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationschemeoptions?view=aspnetcore-5.0
Related
I'm using a server that authenticates through keycloak. In keycloak I have created a realm and am able to get an Access token as a response. This access token gets fed now into my Ktor application.
However, I'm not quite sure how to protect routes in an easy manner. I want to have some protected routes that have a authenticate("keycloakOAuth"){} scope around it which handles validating the access token and refreshing using the refresh token if the access token is expired.
Currently I have keycloak inside Ktor configured as this:
authenticate("keycloakOAuth") {
get("login") {}
route("/callback") {
// This handler will be executed after making a request to a provider's token URL.
handle {
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
if (principal != null) {
val response = principal as OAuthAccessTokenResponse.OAuth2
call.respondText { "Access token: ${response.accessToken}" }
} else {
call.respondText { "NO principal" }
}
}
}
}
This works fine because when I go to login I'm getting sent to the Keycloak login page and I can login. When I logged in the callback executes and I get my Access Token back.
When I'm trying to protect routes however, some odd stuff happens. I know that I need to validate the incoming JWT token. But I have no clue how to given the Ktor capabilities. The examples are also of little help, since they are quite vague.
Currently I have something like this:
authenticate("keycloakOAuth") {
get("/testAuth") {
val principal = call.authentication.principal<OAuthAccessTokenResponse.OAuth2>()
if(principal != null) {
call.respondText("Authenticated!")
} else {
call.respondText("Unauthenticated...")
}
}
}
But my application will always send me to the login page and then callback page, even though I am sending the Bearer token when I'm testing this call.
My question is:
How do I protect routes in a manner that they need a valid token, with the same syntax that Ktor uses (like authenticate(){}). Do I need to configure JWT for this?
When you request one of the routes under authenticate, the full cycle of OAuth authentication is triggered. This is because the Authentication plugin is designed so a client sends credentials and gets authenticated for each request. For some reason, OAuth integration was implemented on top of the Authentication plugin hence such unexpected behavior.
To solve your problem you can have only /login and /callback routes restricted. In the callback's handler save user ID and tokens in a session or in any other storage for future use. For other routes, you can check manually the fact that a user is authenticated and then use tokens from storage to acquire protected data from the resource server. For convenience, you can create some extension functions to minimize the amount of boilerplate code. Unfortunately, there is no built-in functionality to make it work out of the box.
You don't need to configure JWT for this.
I am having a HTML (Angular) site which has a login button and needs (of course) to present a different GUI when the user is authenticated. I am using ServiceStack based REST services. Now when a user is successfully authenticated I was wondering if it is possible to check the generated authentication cookie (by ServiceStack) on the client only. I just need to check the userid, maybe role and expiration date of the cookie. Advantage is I do not have to make 'CheckUserIsAuthenticated' server rest call's just for showing a different GUI (of source CRUD actions are validated serverside).
You can check that a cookie exists with document.cookie, as it's hard to work with directly Mozilla provides a lightweight cookies wrapper to make it easier to work with, likewise AngularJS provides $cookies provider.
But a cookie doesn't tell you anything about the user since even non-authenticated / anonymous users will have cookies. Instead to check if the user is authenticated you can call /auth route via ajax which when authenticated ServiceStack will return summary info about the user, e.g:
{
UserId: "1",
SessionId: "{sessionId}",
UserName: "user#gmail.com",
DisplayName: "First Last"
}
If the user is not authenticated /auth will return 401 Unauthorized.
I understand there is more than one way of handling service authentication/authorization, but I cannot make it work for static files.
Is there a way of configuring the behavior to be the same as with services; if not authenticated a request to index.html should redirect to login page the same as a request to secured dto/service.
I am currently looking into RawHttpHandlers but since it is too early in the pipeline how do I get the authentication setup in the apphost config?
thanks in advance
Gjergji
You would have to use IAppHost.RawHttpHandlers because that's the only custom handler in ServiceStack's Request Pipeline that gets executed before the built-in static file handling is accessed.
But you should still be able to access the Users Session with the available extension methods, e.g:
this.RawHttpHandlers.Add(httpReq =>
{
var isStaticFileRequest = httpReq.PathInfo.StartsWith("/static");
if (isStaticFileRequest)
{
var session = httpReq.GetSession();
if (!session.HasRole("TheRole"))
return new ForbiddenHttpHandler();
}
return null;
});
This handler simply checks if it's a request for a static file, in this case the path info starts with /static, and if is checks the user session if they have the required role, if not it returns a Forbidden request, otherwise it returns null to tell ServiceStack to continue executing the request.
Note: if it's needed you can access any registered dependency from outside of ServiceStack with HostContext.Resolve, e.g:
var authRepo = HostContext.Resolve<IAuthRepository>();
I am having a few problems trying to connect to a ASP.NET webapi service (which I am running myself) from a sample console app using WebClient. The webapi is the typical sample site from MVC4:
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, new string[] { "value1", "value2" });
}
The Controller is decorated with a custom Authenticate attribute:
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.Headers.Add("WWW-Authenticate", "Basic realm=\"localhost\"");
actionContext.Response = response;
return;
}
}
The client code is the usual:
var wb = WebRequest.Create("http://localhost:64921/Values");
wb.Credentials = new NetworkCredential("xxx", "xxx");
var aaa = wb.GetResponse();
Console.WriteLine(aaa);
Console.ReadLine();
Now, I know that the WebClient or WebRequest are supposed to wait for a 401 before sending credentials and that is exactly what I am trying to do here.
Needless to say with the setup above nothing works. I have gone into the IIS express config and changed the following:
<basicAuthentication enabled="true" /> (in the security section)
<add name="BasicAuthenticationModule" lockItem="false" /> (in the modules section)
The problem that I am having is that the 401 gets returned even before the server code is actualy hit. I mean that if I stick a breakpoint into the Controller or the Attribute they are not hit. The details of the error are the usual long text about error 401.2 which I reckon is something to do with IIS configs, but using IIS express and not the nice IIS I do not have a nice GUI to fix this. Can anyone help?
Thanks a lot!
In the IIS config, you have enabled Basic auth processing, so IIS returns the 401 if there are no credentials or the credentials are invalid.
If you want your code to do the basic auth processing, then you need to tell IIS to allow anonymous access.
EDIT from comments
If you ask IIS to do basic auth it will check credentials against Windows accounts. This will act before the server code runs, so the Custom Auth Filter will not be hit. In this case the headers returned will be correct and you will see the WebClient performing the double request (one anonymous, one with credentials). If the WebClient does not use a computer or domain account (with read permissions on the folder where the site is located), the request will fail.
If you want to do authentication/authorization yourself, you need to tell IIS express not to do any auth and then do it all yourself... this basically means leaving everything as it is in the config (in your case reverting the pieces of config shown in the question) and sending the correct headers, which you already do. If you debug, you will see the Authenticate filter being hit twice, the first time it will be an anonymous that will go inside the if and generate your HTTP 401 Challenge response, the second time it will have credentials in the form of a standard Basic Authorization header: Basic <BASE64_ENCODED_CREDENTIALS>
I have set up a servicestack service with basic authentication using the first example, here:
https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization
This automatically sets up a route: /auth/basic
However, I cannot find any information or examples on how to format a request to this URL (Variables/GET/POST/Auth Header, etc.).
I am able to access a simple service using the basic authentication credentials, so they are active and correct.
I have no custom authentication plugged in, just basic authentication.
I have tried:
Using a JsonServiceClient to send UserName and Password variables by GET or Json POST to /auth/basic, with and without an Auth header also containing the user & pass.
Using a browser to send GET requests with URL parameters of the user/pass, or as http://user:pass#localhost:123/auth/basic
I always just get "HTTP/1.1 401 Invalid BasicAuth credentials".
The only examples I can find involve some kind of custom authentication, and then /auth/credentials is accessed, but I want to use /auth/basic
I have looked at the code and it looks like it reads an Auth header, but the service does not accept one.
I am actually trying to get this working so I can then disable it and verify it is disabled (I want to require basic authentication for every request).
Questions are:
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
How do you disable the /auth services altogether?
Many thanks.
What is the correct way to call the /auth/basic service? I will take a servicestack client API example, specifications or even a raw http request!
var client = new JsonServiceClient("http://localhost:56006/api");
var resp = client.Post(new Auth() { UserName = "TestUser", Password = "Password" });
This assumes you have also registered an ICacheClient and IAuthUserRepository (and added a user account)
The JSON format looks like this if you call into /auth/basic?format=json
{
"UserName": "admin",
"Password": "test"
"RememberMe": true
}
How do you disable the /auth services altogether?
Don't add the AuthFeature plugin to configuration.
You can also remove plugins
Plugins.RemoveAll(x => x is AuthFeature);
Putting the following in apphost config seems to do the trick.
//Disable most things, including SOAP support, /auth and /metadata routes
SetConfig(new EndpointHostConfig()
{
EnableFeatures = Feature.Json | Feature.Xml
});
I am a little suspicious about what this does to /auth however, because it returns an empty response, while most routes return 404.
So, would this truly disable the /auth functionality? As in, if someone formed a correct request to /auth/credentials, will it still return an empty response?