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.
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.
Is it possible for a ServiceStack api to accept jwt tokens from multiple identity providers?
I have one admin application that will be calling all our apis across environments. I need to allow my api's to accept jwt tokens from two different identity providers. This can be accomplished in web api, by calling the .AddJwtBearer help method twice and not providing a default schema in the AddAuthentication() helper. And the providing both in the AddAuthorization helper method. I tested this out in ServiceStack and it is not working for me.
This is in the .net core startup, configure services.
services.AddAuthentication()
.AddJwtBearer("Bearer", options => {
options.Authority = Configuration["IDENTITYSRV_WEB_BASEURL"];
options.RequireHttpsMetadata = Boolean.Parse(Configuration["IDENTITY_HTTPSMETADATA"]);
options.Audience = Configuration["IDENTITY_VALIDAUDIENCE"];
})
.AddJwtBearer("Admin", options =>
{
options.Authority = "Configuration["IDENTITYSRV_WEB2_BASEURL"]";
options.RequireHttpsMetadata = Boolean.Parse(Configuration["IDENTITY_HTTPSMETADATA"]);
options.Audience = Configuration["IDENTITY_VALIDAUDIENCE"];
});
AppHost
AuthFeature auth = new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] { new NetCoreIdentityAuthProvider(AppSettings), })
{
IncludeAssignRoleServices = false,
IncludeRegistrationService = false,
IncludeAuthMetadataProvider = false
};
Plugins.Add(auth);
Any suggestions or work around?
ServiceStack's JWT Auth Provider supports all HMAC and RSA crypto algorithms for its JWT or JWE tokens, but it can only be configured with the 1 algorithm you want it to use. So it's technically not possible for it to verify JWT Tokens from different authorities which would likely be configured with different keys and algorithms.
The next obstacle is that if all Identity providers were configured to use the same Key and Algorithm, they would need to encode the same contents ServiceStack uses in order for the JWT to be correctly deserialized into an Authenticated ServiceStack Session. Most of the names used in ServiceStack's JWT's have well-known names however there are others that don't like roles and perms, in which case ServiceStack JWT's have adopted the Azure Active Directory Conventions. Although there is an opportunity to apply custom logic when inspecting the verified JWT Body and populating the Session using the PopulateSessionFilter.
Ultimately I don't think trying to funnel multiple JWT's into the same implementation is a viable strategy, instead I would be looking at creating Custom JWT Auth Providers like JwtAuthProviderReader.cs which just knows how to handle parsing JWT's from a single provider which would then know how to verify & extract the Authenticated Session Info from each JWT and use it to populate a ServiceStack Session. This could be done in a "single uber JWT Auth Provider" that has knowledge in being able to parse every JWT sent to it, but as JWT Tokens are sent with the same Authorization: Bearer {Token} HTTP Header there would need to be some information in the JWT Headers that determines which crypto algorithms it should use to validate each token. If all Identity Providers use different alg then that might be enough to be able to distinguish the identity provider.
Either way there's no pre-built solution in ServiceStack that you could configure to support multiple identity Auth Providers, the other approach you may want to look at is to maintain all Authentication in a central Identity Server Auth Provider so then your Websites only need to be configured to support Authentication from the central Identity Server.
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 { ... }
We have a SharePoint publishing site with anonymous access hosted on the internet. As per out latest requirements, we need to implement user login (AzureAD, Microsoft personal and work accounts, and more).
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
As per the documentation here, we want to implement this using Web API to get the secure information from the database. We are thinking about using MSAL.js file for user login and logout on the SharePoint and after getting a bearer token we can call the Web API for the additional data from our database.
Standalone Web APIs restriction: “You can use the v2.0 endpoint to build a Web API that is secured with OAuth 2.0. However, that Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID. The client won't be able to request or obtain permissions to your Web API.”
How can we create two applications with same application ID at App Registration Portal? Or should we use the same application ID at SharePoint and Web API’s end?
There is no need to register two application, you only need to one register application. After you register the application, you can using the MSAL library below to get the token to call the web API:
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("e5e5f2d3-4f6a-461d-b515-efd11d50c338", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser();
console.log(token);
// signin successful
}, function (error) {
// handle error
});
</script>
And to protect the web API, you can use the same app and refer the code below:
public void ConfigureAuth(IAppBuilder app)
{
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = "e5e5f2d3-4f6a-461d-b515-efd11d50c338",
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}
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.