I have set up an authentication server that issues JWT Tokens.
I now have setup my first resource service that will authenticate/authorize using the bearer token provided in a request. This service is not my auth server, it is a resource server.
I added the ServiceStack JwtAuthProviderReader to my resource service:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new JwtAuthProviderReader() {
HashAlgorithm = "HS256",
AuthKeyBase64 = AuthSettings.JwtAuthKeyBase64
},
}));
Why does my resource server now have all the auth server endpoints, I am using the JwtAuthProviderReader, not the JwtAuthProvider that my auth service does. As the documentation states, my resource service is only validating tokens.
These aren't limited to just the JWT AuthProvider, they're ServiceStack's built-in Auth Services for handling any ServiceStack Authentication, i.e. when registering the ServiceStack's AuthFeature plugin.
If you're not using Assign/Unassign Roles Services, they can be disabled with:
Plugins.Add(new AuthFeature(...) {
IncludeAssignRoleServices = false
});
You can also hide Services from showing up in the metadata pages and Services by dynamically adding Exclude attributes in the AppHost's constructor, e.g:
public AppHost() : base("MyApp", typeof(MyServices).Assembly)
{
typeof(Authenticate)
.AddAttributes(new ExcludeAttribute(Feature.Metadata));
}
Which is equivalent to adding the attribute on the Request DTO, e.g:
[Exclude(Feature.Metadata)]
public class Authenticate { ... }
Related
I'm hosting my API in Azure and configured API Management for authentication and authorization. Do I still need to include the [Authorize] attribute on my api controllers? If so, what would I need in the Startup class to allow access when calling through Azure, but be unauthorized if call the endpoints directly?
[ApiController]
[Route("api/[controller]")]
public class TestController : BaseController
As per my understanding from your question you can take up in this way.
Still go with [Authorize], as after hosting in APIm stil app need to authorize the user, post authentication.
Authentication will be there in startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = <<Pass your authority>>;
options.Audience = <<Pass audience>>;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
};
});
Use Validate Jwt Inbound policy in APIM.
But if the user is able to generate a required Bearer token then it should be able to access. If you want to restrict if it's not from APIM then you can check the APIM subscription Key in the header & can decline the user request.
Here is my auth config
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[]
{
new ApiKeyAuthProvider(AppSettings) {RequireSecureConnection = false }
})
{
IncludeRegistrationService = true,
});
Global filter
GlobalRequestFilters.Add((req, res, requestDto) =>
{
LastApiKey = req.GetApiKey();
});
I have generated Api key in DB and trying to pass the key through postman or JsonServiceClient with Basic Authentication having API key in Username of Http request. I tried to pass bearer token in Http request too but not able to get api key from req.GetApiKey(). When I see through SQL profiler it doesn't show any query executed. However I am able to resolve IManageApiKeys and able to execute it's methods. Do I need any attribute in my service or any additional configuration in AppHost?
ServiceStack's API Key Auth Provider works by setting up an Authenticated User Session on the Request for successful API Key Authentication requests. When setting up the Authenticated User Session the API Key that was used to authenticate is stored in IRequest.Items Dictionary which is what IRequest.GetApiKey() Extension method is returning.
So IRequest.GetApiKey() isn't making any DB calls, that was already done initially when setting up the Authenticated User Session.
Authentication is performed when it's required, e.g. when your Services are annotated with any of the Authentication Attributes below:
[Authenticate]
[RequiredRole] / [RequiresAnyRole]
[RequiredPermission] / [RequiresAnyPermission]
I've setup an Azure Mobile App Service backend and there is a Xamarin app consuming it's services. It uses custom authentication in the Azure Mobile App Service for registering and authenticating the users of the app.
For local development/debugging application's OWIN startup class contains some code to setup the authentication options of the app service, as described in https://azure.microsoft.com/nl-nl/documentation/articles/app-service-mobile-dotnet-backend-how-to-use-server-sdk/#local-debug.
In Azure the Mobile App Service's authentication is enabled (Authentication / Authorization) setting the 'Action to take when request is not authenticated' option to 'Allow request (no action)' so the application handles the request authentication.
This all works as desired.
Now we would like to support a custom domain on our Mobile App Service and support the current ourmobileappservice.azurewebsites.net domain. We've configured the custom domain, configured it's SSL certificate and all works well. New tokens are issued with the custom domain as audience/issuer and it's also validated in this manor.
But when issuing a token with ourmobileappservice.azurewebsites.net as audience/issuer, it's rejected during token validation. It seems only our custom domain is allowed as valid audience.
For local development we're specifying the app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { ... }), also setting the ValidAudiences property. So I wanted to use this setup for the Azure environment as well, so we can specify multiple valid audiences for token validation. Note: of course the AppServiceAuthenticationOptions is different than for local development, e.g. SigningKey comes from Azure's environment variables).
Unfortunately Azure doesn't seem to use this at all. It still keeps failing in the exact same way. As you can see only the custom domain is specified as valid audience:
Warning JWT validation failed: IDX10214: Audience validation
failed. Audiences: 'https://ourmobileappservice.azurewebsites.net/'.
Did not match: validationParameters.ValidAudience:
'https://ourcustom.domain.com/' or
validationParameters.ValidAudiences: 'null'.
How can I configure the Azure Mobile App Service with custom authentication setup so it's valid audiences supports both the custom domain as the previous ourmobileappservice.azurewebsites.net?
Edit
The valid audiences are specified for Azure as follows:
public static void ConfigureMobileApp(IAppBuilder app)
{
...
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
ValidIssuers = new[] { "https://ourcustom.domain.com/", "https://ourmobileappservice.azurewebsites.net/" },
TokenHandler = config.GetAppServiceTokenHandler()
});
...
}
You can sign the token with any URL which you like in your custom auth provider. Whatever you specify in the AppServiceLoginHandler.CreateToken() method will go into the JWT.
When it comes to validation of the token, if you're debugging locally the list of URLs specified in the middleware will be used to validate the audience and issuer. In production Azure will magically use your default Azure domain and custom domains.
The way I did it was to create a new web.config AppSetting which contains the valid signing URLs for all environments.
<add key="ValidUrls" value="https://api.myproductiondomain.com/, https://myproductionapp.azurewebsites.net/, http://localhost:59475/" />
In the Startup.MobillApp.cs I'm populating the valid audiences and issuers from this list.
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = validUrls,
ValidIssuers = validUrls,
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Now in my login method before generating the token, I'm checking that the hostname of the current request is in the same AppSetting whitelist. If it's valid use the current hostname as the Audience and Issuer for my token.
Something like this;
// Get current URL
var signingUrl = $"{this.Request.RequestUri.Scheme}://{this.Request.RequestUri.Authority}/";
// Get list from AppSetting
var validUrls = ConfigurationManager.AppSettings["ValidUrls"].Split(',').Select(u => u.Trim()).ToList();
// Ensure current url is in whitelist
if (!validUrls.Contains(signingUrl))
{
return this.Request.CreateUnauthorizedResponse();
}
var claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id),
};
var signingKey = this.GetSigningKey();
// Sign token with this
var audience = signingUrl;
var issuer = signingUrl;
// Set expirey
var expiry = TimeSpan.FromHours(72);
// Generate token
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
claims,
signingKey,
audience,
issuer,
expiry
);
We actually just made a set of updates which allow you to set audiences in the portal. If you return to the AAD settings under App Service Authentication / Authorization, you should see some new options under the "Advanced" tab. This includes an editable list of allowed token audiences.
If you add https://ourmobileappservice.azurewebsites.net to that list, you should be good to go.
I cannot generate proxy class when authentication provider is enabled. All DTOs in ServiceStack must be in the same namespace. I get that. Here's where this becomes a problem. Enabling authentication
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] { new BasicAuthProvider()} ));
adds a few ServiceStack resources - Auth, AssignRoles and so on. These DTOs are in the ServiceStack default namespace http://schemas.servicestack.net/types. Now I am stuck - even with the Config.WsdlServiceNamespace and ContractNamespace tricks described in https://github.com/ServiceStack/ServiceStack/wiki/SOAP-support I cannot get svcutil to build a proxy class because my DTOs and ServiceStack provided (which are required for authentication) are in different namespaces.
Can I have authentication provider and SOAP at the same time?
Is there any (or going to be any) built in support for declaratively securing (i.e. using attributes) REST services for oAuth2?
I would like to specify that the REST services of my SS web service can only be accessed by a client if they specify the oAuth2 'Authorization' header in their request to the service.
I don't want my service to provide authentication to my clients (i.e. no AuthFeature). Clients need to have already done authentication with a oAuth service (i.e. facebook etc.).
Using the [Authenticate] attribute on your Service ensures that only authenticated clients have access.
The Authentication wiki explains how to initialize ServiceStack's built-in AuthFeature to specify only the providers you want to allow clients to authenticate with, e.g. You can ensure clients can only Authenticate with either LinkedIn or Google OAuth2 providers with:
var appSettings = new AppSettings(); //Access Web.Config AppSettings
Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new GoogleOAuth2Provider(appSettings), //Sign-in with Goolge OAuth2
new LinkedInOAuth2Provider(appSettings), //Sign-in with LinkedIn OAuth2
}));
Note: OAuth2 requires the additional ServiceStack.Authentication.OAuth2 NuGet package and Web.Config settings, see Auth docs for more info.
Using Request Filters
You can also enforce specific requirements for client requests by a Global Request Filter or opt-in Request Filter Attributes, e.g:
this.RequestFilters.Add((httpReq, httpRes, requestDto) => {
var authHeader = httpReq.Headers[HttpHeaders.Authorization];
if (!IsValidAuthHeader(authHeader)) {
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
httpRes.StatusDescription = "Authentication is required";
httpRes.EndRequest();
}
});
More Service Restrictions
Also related are the Security docs describes how you can declaratively restrict services using the [Restrict] attribute.