ServiceStack basic authentication - servicestack

I'm fairly new with ServiceStack authentication bit. First I configured basic authentication:
private void ConfigureAuth(Funq.Container container)
{
var authFeature = new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] { new BasicAuthProvider() }
);
authFeature.IncludeAssignRoleServices = false;
// Default route: /auth/{provider}
Plugins.Add(authFeature);
container.Register<ICacheClient>(new MemoryCacheClient());
container.Register<IUserAuthRepository>(GetAuthRepository());
}
How to authenticate with a service request? for example: myweb/api/auth/basic?Userid=test#Password=234
The authentication service endpoint in protected itself. calling myweb/api/auth/basic?Userid=test#Password=234 will redirect /Account/LogOn
I need a very simple authentication mechanism. Clients can simply authenticate by sending a JSON request.

See ServiceStack AuthTests for examples on how to authenticate with Basic Auth.

Related

Azure App Service with websockets and AD authentication

we got an application deployed as App Service and we are using SignalR for communication. After enabling AAD authentication - in browsers we started receiving 302 responses with redirect location to Azure AD.
Seems like the authentication layer on App Service is ignoring access_token passed by query string.
Request
Request URL: wss://<url>/hubs/chat?access_token=<token>
Request Method: GET
Response
Status Code: 302 Redirect
Location: https://login.windows.net/common/oauth2/authorize?...
After looking everywhere we couldn't find any solution to make this work.
The only solution to this issue that we see is either to disable authentication on App Service or use Long-Pooling, but both options are not acceptable in our situation.
By default, you web application will not get the access token from query string. Commonly, it will get the access token from authorization header or the cookie.
To get the access token from query string, you need to implement your custom authentication way.
Install Microsoft.Owin.Security.ActiveDirectory NuGet package.
Create an authentication provider which will get access token from query string.
public class QueryStringOAuthBearerProvider : OAuthBearerAuthenticationProvider
{
public override Task RequestToken(OAuthRequestTokenContext context)
{
var value = context.Request.Query.Get("access_token");
if (!string.IsNullOrEmpty(value))
{
context.Token = value;
}
return Task.FromResult<object>(null);
}
}
Add map in .
app.Map("/yourpath", map =>
{
map.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Provider = new QueryStringOAuthBearerProvider(),
Tenant = tenantId,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId
}
});
map.RunSignalR(hubConfiguration);
});
After multiple calls with Microsoft Technical Support, MS confirmed that App Service Authentication layer doesn't support access token passed in query string and there are no plans for this support yet. So there are two options:
Use different protocol for SignalR (long pooling works just fine)
Drop App Service Authentication
Using a custom middleware, I was able to update the request prior to authorization occurring:
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Stackoverflow.Example.Security.Middleware
{
public class BearerTokenFromQueryToHeaderMiddleware
{
private readonly RequestDelegate _next;
public BearerTokenFromQueryToHeaderMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var token = context.Request.Query["access_token"];
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", $"Bearer {token}");
}
await _next(context);
}
}
}
I didn't try to get this working with the OpenID framework, but I did test using a custom policy. As long as this is registered earlier than the authentication, then this middleware should execute prior to the framework looking for the token in the header.

Kentor/Owin/Azure AD Authentication

I have a web forms app which I am trying to authenticate against Azure AD using SAML 2/Kentor/Owin. I think I have things configured OK, but when my login page issues the following command I am not being redirected to a login page.
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Login.aspx" });
Here is my startup.cs
private void ConfigureSAML2Authentication(IAppBuilder app) {
var authServicesOptions = new KentorAuthServicesAuthenticationOptions(false)
{
SPOptions = new SPOptions
{
EntityId = new EntityId("https://login.microsoftonline.com/<tenant guid>/saml2")
}
},
AuthenticationType = "KentorAuthServices",
Caption = "ADFS - SAML2p",
};
authServicesOptions.IdentityProviders.Add(new IdentityProvider(
new EntityId("https://sts.windows.net/<tenant guid>/"),
authServicesOptions.SPOptions)
{
MetadataLocation = "https://login.microsoftonline.com/<tenant guid>/federationmetadata/2007-06/federationmetadata.xml",
LoadMetadata = true,
});
app.UseKentorAuthServicesAuthentication(authServicesOptions);
}
As far as I can tell looking at the Network Tools in chrome, no auth request is being sent at all. Is anyone able to tell me why?
The AuthServices middleware is configured as Passive by default, so it will not automatically respond to an authentication challenge unless you specify the provider.
When you issue the challenge you should specify the same AuthenticationType that you used when the middleware was set up. By default this is "KentorAuthServices" but can be changed.
If you change your challenge to include the type, it should trigger the redirect:
HttpContext.Current.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/Login.aspx" }, "KentorAuthServices");

UNAUTHORIZED with API key

I try to authenticate a call from a service to another service using an API key. An administrative service creates 'service account users' when it is started for the first time. Now when a service calls another service I have:
Client = new JsonServiceClient("http://TheOtherServer:1234")
{
Credentials = new NetworkCredential(<the string with my api key>, ""),
};
//.....
var request = new RequestDtoOfOtherServer
{
//set some request props
};
try
{
var result = Client.Get(request);
//do something with result
}
catch (Exception ex)
{
Log.Error($"Error: {ex}");
throw;
}
Whatever key I use from the 2 keys issued for the calling service user, I always get a 401 UNAUTHORIZED error. I turned on the RequestLogsFeature on the receiving service but there is NO entry.
The method I call is annotated with [RequiresAnyRole("User", "Administrator", "bizbusops-service", "SYSTEM")] and the user which is related to the API key I use is in the Role bizbusops-service. Also when I use my WPF UI and login with that user (with username / password) I can access this method without error. So there must be something wrong with establishing the server-to-server connection and / or the API key.
What am I missing?
Does the above code with NetworkCredential establish a session between the two servers and issue a cookie?
I see in the Redis DB that two keys are issued to the user account of the service. Can I use both of them or do I have to set the Environment and KeyType somewhere on the server side, e.g. in a RequestFilter?
UDATE
On the server which receives the authentication calls I have configured the AuthFeature Plugin like so:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BizBusAuthProvider(),
new ApiKeyAuthProvider(AppSettings)
{
KeyTypes = new []{"secret", "publishable"},
},
}
));
This configuration generated 4 API keys for every new user, the ones defined above and the two created by default.
If you're going to use Credentials to send the API Key then you'll need to register the ApiKeyAuthProvider so it's the first AuthProvider listed, e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new ApiKeyAuthProvider(AppSettings)
{
KeyTypes = new []{"secret", "publishable"},
},
new BizBusAuthProvider(),
}
));
This is so when .NET's WebRequest receives a 401 WWW-Authenticate challenge response it will automatically add the Credentials when retrying the Request.
Otherwise you can use a BearerToken to send the API Key, e.g:
var client = new JsonServiceClient(baseUrl) {
BearerToken = apiKey
};

ServiceStack and OAuth2

How can I use the existing servicestack oauth2 providers, google for example, and only limit it to one account that I create for my users?
Basically, I want to keep the Api access under check, so that not everyone who has a google account can use it.
You can use the CustomValidationFilter to add your own Custom Validation, returning a non null response will cancel Authentication and return the desired error response, e.g:
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new GoogleOAuth2Provider(appSettings) {
CustomValidationFilter = authCtx => {
if (!AllowUser(authCtx.Session, authCtx.AuthTokens)) {
var url = authCtx.AuthProvider.GetReferrerUrl(
authCtx.Service, authCtx.Session
).AddParam("f","GoogleOAuthNotAllowed");
return authCtx.Service.Redirect(url);
}
return null; //Allow Authentication through
}
},
}));

How to enable basic authentication without user sessions with ServiceStack?

According ServiceStack github wiki In order to add/enable basic authentication in ServiceStack following lines of code are required:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new BasicAuthProvider(), //Sign-in with Basic Auth
new CredentialsAuthProvider(), //HTML Form post of UserName/Password credentials
}));
But how can I add basic authentication without user sessions?
If you want to perform the authentication without using sessions then you can create a simple request filter that performs the basic authentication yourself.
You can then authenticate the credentials either against your own database or repositor, or you can authenticate against the standard ServiceStack repository shown below:
public class MyAuthenticateAttribute : RequestFilterAttribute
{
public override void Execute(IRequest req, IResponse res, object requestDto)
{
// Determine if request has basic authentication
var authorization = req.GetHeader(HttpHeaders.Authorization);
if(!String.IsNullOrEmpty(authorization) && authorization.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
// Decode the credentials
var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authorization.Substring(6))).Split(':');
if(credentials.Length == 2)
{
// Perform authentication checks. You could do so against your own database
// or you may wish to use the ServiceStack authentication repository IUserAuthRepository
// If you want to check against ServiceStacks authentication repository
var repository = HostContext.TryResolve<IUserAuthRepository>();
if(repository == null)
throw new Exception("Authentication Repository is not configured");
// Try authenticate the credentials
IUserAuth user;
if(repository.TryAuthenticate(credentials[0], credentials[1], out user))
{
// Authenticated successfully
// If you need the user details available in your service method
// you can set an item on the request and access it again in your service
// i.e. req.SetItem("user", user);
// In your service: Request.GetItem("user") as IUserAuth
return;
}
}
}
// User requires to authenticate
res.StatusCode = (int)HttpStatusCode.Unauthorized;
res.AddHeader(HttpHeaders.WwwAuthenticate, "basic realm=\"My Secure Service\"");
res.EndRequest();
}
}
So instead of using the [Authenticate] attribute you would use the [MyAuthenticate] attribute.
In your AppHost Configure method do not add the AuthFeature plugin. You do still however need to add the repository, if that's how you choose to authenticate the credentials against.
container.Register<ICacheClient>(new MemoryCacheClient());
var userRep = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRep);
I hope this helps.

Resources