ServiceStack and OAuth2 - servicestack

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
}
},
}));

Related

API returning 401 on Azure but not when run locally

The application in question is a web site which has an API tacked on the side that reuses the many of the data access methods developed for the website. So there maybe some interference between the web site authentication/authorization and the API's. But if that was the case I don't understand why it works locally.
When I run locally, I can test the API using Swagger or Postman to login, get the Bearer token and use it to call the API methods. On Azure, although the login succeeds the next call to the API returns a 401:
www-authenticate: Bearer error="invalid_token",error_description="The signature is invalid"
The most obvious difference is the appsettings.json which I've copied to the Application Configuration blade on Azure:
The original appsettings looked like:
"Rt5": {
"BaseAddress": "https://localhost:44357"
},
"Azure": {
"BuildNumber": ""
},
"AuthentificationJWT": {
"Audience": "ddt-ssp",
"Issuer": "ddt-rt5",
"SecurityKey": "not so secret"
},
Usage - ConfigureServices():
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/Home/Unauthorized";
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthentificationJWT:SecurityKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AuthentificationJWT:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AuthentificationJWT:Audience"],
ValidateLifetime = true,
ValidAlgorithms = new HashSet<string> { SecurityAlgorithms.HmacSha256 },
ValidTypes = new HashSet<string> { "JWT" }
};
});
services.AddAuthorization(o =>
{
o.AddPolicy(AllowViewExceptions.Policy, p
=> p.Requirements.Add(new AllowViewExceptions.Requirement()));
o.AddPolicy(AllowSubmitException.Policy, p
=> p.Requirements.Add(new AllowSubmitException.Requirement()));
});
Usage - Configure():
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
if (UseSwagger)
{
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); });
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Confession: this isn't my code and I'm sure why we have the Cookie stuff mentioned though is does seem to be offer an alternative authorization mechanism. See here:
public string GetAuthorization()
{
//TODO: use ExtractSecurityToken();
var claimToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName);
if (claimToken != null)
{
return $"Bearer {claimToken.Value}";
}
else if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var headerToken))
{
return headerToken;
}
return null;
}
private JwtSecurityToken ExtractSecurityToken()
{
var accessToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName)?.Value;
if (accessToken == null &&
_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var value))
{
accessToken = value.ToString().Replace("Bearer ", ""); // TODO: Find a better way then extracting Bearer e.g Get token without scheme
}
if (accessToken == null)
{
return null;
}
return (JwtSecurityToken)new JwtSecurityTokenHandler().ReadToken(accessToken);
}
The problem appears to be with Azure Websites' "Authentication / Authorization" option, which when enabled prevents the Web Api from accepting requests with the Authentication header. Disabling the option and using the Owin library with Azure AD provides the desired solution.
Every app service that is associated with Azure-AD has a corresponding Azure-AD application declaration of type Web app/API. This resource id is the "App ID URI" in the app service's Azure-AD application declaration.
To debug Authentication issues, please refer this msdn link.
It turned out the problem was caused by the bizarre architecture surround the application. The Access Token value (including the SHA256 checksum calculated using the security key) was being generated in another application and passed to this one to use for its communications.
When run localling both applications were using a security key stored in their appsettings.json file and were the same. The pipeline which deployed these applications to Azure was substituting security keys for random values and so they differed.
I've left the question here undeleted because Suryasri's link may help others later.

Multiple AuthProvider Servicestack

I am having some issue to make work 2 auth provider at the same time for servicestack.
I am using the : JWT Tokens - Allowing users to authenticate with JWT Tokens I am my users get authenticate fine.
Still Now I would like to use the API Keys - Allowing users to authenticate with API Keys for a few external 3rd Parties user access.
Still when I Configure both my users allready authenticate by JWT Tokens doesnt work anymore.
Here is my configuration AuthProvider configuration :
IAuthProvider[] providers = new IAuthProvider[]
{
new JwtAuthProviderReader(this.AppSettings)
{
HashAlgorithm = "RS256",
PrivateKeyXml = this.AppSettings.GetString("TokenPrivateKeyXml"),
PublicKeyXml = this.AppSettings.GetString("TokenPublicKeyXml"),
RequireSecureConnection = this.AppSettings.Get<bool>("TokenUseHttps"),
EncryptPayload = this.AppSettings.Get<bool>("TokenEncryptPayload"),
PopulateSessionFilter = (session, obj, req) =>
{
ApplicationUserSession customSession = session as ApplicationUserSession;
if (customSession != null)
{
customSession.TimeZoneName = obj["TimeZoneName"];
customSession.Type = (FbEnums.UserType) (obj["UserType"].ToInt());
if (Guid.TryParse(obj["RefIdGuid"], out Guid result))
{
customSession.RefIdGuid = result;
}
}
},
},
new ApiKeyAuthProvider(AppSettings)
{
RequireSecureConnection = false
}
};
I am genereting fine the token with JwtAuth. Still It look like servicestack is not accepting my token as a valid session, because now whenever I do :
var session = httpReq.GetSession();
session.IsAuthenticated --> is always FALSE
If my remove ApiKeyAuthProvider from the configuration, token from JwtAuth working fine again.
How do I make both provider works together and tell servicestack tham some users will use JwtAuth and others ApiKeyAuth ?
You need to call a Service that requires Authentication, e.g. has the [Authenticate] attribute in order to trigger pre-Authentication for the IAuthWithRequest providers like JWT and API Key AuthProviders.

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
};

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.

ServiceStack basic authentication

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.

Resources