Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#842 AuthenticationOptions in GlobalConfiguration #1215

57 changes: 57 additions & 0 deletions docs/features/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,63 @@ When Ocelot runs it will look at this Routes AuthenticationOptions.Authenticatio

If a Route is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401.

If you want to configure AuthenticationOptions the same for all Routes, do it in GlobalConfiguration the same way as for Route. If there are AuthenticationOptions configured both for GlobalConfiguration and Route (AuthenticationProviderKey is set), the Route section has priority. If you want to exclude route from global AuthenticationOptions, you can do that by setting AllowAnonymousForGlobalAuthenticationOptions to true in the route AuthenticationOptions - then this route will not be authenticated.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to write a new subsection of the chapter like:

Global Authentication
^^^^^^^^^^^^^^^^^^^^^

before this line.

And this line is too long! Splitting phrases should help.


In the following example the first route will be authenticated with TestKeyGlobal provider key, the second one - with TestKey provider key, the others will not be authenticated.

.. code-block:: json

"Routes": [
{
"DownstreamPathTemplate": "/abc",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 54001
}],
"UpstreamPathTemplate": "/abc",
"UpstreamHttpMethod": [ "Get" ]
},
{
"DownstreamPathTemplate": "/def",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 54001
}],
"UpstreamPathTemplate": "/def",
"UpstreamHttpMethod": [ "Get" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "TestKey",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/{action}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 54001
}],
"UpstreamPathTemplate": "/{action}",
"UpstreamHttpMethod": [ "Get" ],
"AuthenticationOptions": {
"AllowAnonymousForGlobalAuthenticationOptions": true
}
}
],
"GlobalConfiguration": {
"BaseUrl": "http://fake.test.com",
"AuthenticationOptions": {
"AuthenticationProviderKey": "TestKeyGlobal",
"AllowedScopes": []
}
}


JWT Tokens
^^^^^^^^^^

Expand Down
18 changes: 12 additions & 6 deletions src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
using Ocelot.Configuration.File;

using System.Linq;

namespace Ocelot.Configuration.Creator
{
public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator
{
public AuthenticationOptions Create(FileRoute route)
{
return new AuthenticationOptions(route.AuthenticationOptions.AllowedScopes, route.AuthenticationOptions.AuthenticationProviderKey);
}
public AuthenticationOptions Create(FileAuthenticationOptions routeAuthOptions,
FileAuthenticationOptions globalConfAuthOptions)
Comment on lines +8 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public AuthenticationOptions Create(FileAuthenticationOptions routeAuthOptions,
FileAuthenticationOptions globalConfAuthOptions)
public AuthenticationOptions Create(
FileAuthenticationOptions routeAuthOptions,
FileAuthenticationOptions globalConfAuthOptions)

Or, Don't make new line at all. Having 2-3 parameters in signature on the same line is totally fine.

{
var routeAuthOptionsEmpty = string.IsNullOrEmpty(routeAuthOptions.AuthenticationProviderKey);

var resultAuthOptions = routeAuthOptionsEmpty ? globalConfAuthOptions : routeAuthOptions;

return new AuthenticationOptions(resultAuthOptions.AllowedScopes, resultAuthOptions.AuthenticationProviderKey);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator
{
public interface IAuthenticationOptionsCreator
{
AuthenticationOptions Create(FileRoute route);
AuthenticationOptions Create(FileAuthenticationOptions routeAuthOptions, FileAuthenticationOptions globalConfAuthOptions);
}
}
}
2 changes: 1 addition & 1 deletion src/Ocelot/Configuration/Creator/IRouteOptionsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace Ocelot.Configuration.Creator
{
public interface IRouteOptionsCreator
{
RouteOptions Create(FileRoute fileRoute);
RouteOptions Create(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration);
}
}
11 changes: 8 additions & 3 deletions src/Ocelot/Configuration/Creator/RouteOptionsCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ namespace Ocelot.Configuration.Creator
{
public class RouteOptionsCreator : IRouteOptionsCreator
{
public RouteOptions Create(FileRoute fileRoute)
public RouteOptions Create(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
{
var isAuthenticated = IsAuthenticated(fileRoute);
var isAuthenticated = IsAuthenticated(fileRoute, globalConfiguration);
var isAuthorized = IsAuthorized(fileRoute);
var isCached = IsCached(fileRoute);
var enableRateLimiting = IsEnableRateLimiting(fileRoute);
Expand All @@ -26,7 +26,12 @@ public RouteOptions Create(FileRoute fileRoute)

private static bool IsEnableRateLimiting(FileRoute fileRoute) => fileRoute.RateLimitOptions?.EnableRateLimiting == true;

private static bool IsAuthenticated(FileRoute fileRoute) => !string.IsNullOrEmpty(fileRoute.AuthenticationOptions?.AuthenticationProviderKey);
private static bool IsAuthenticated(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
{
return (!string.IsNullOrEmpty(globalConfiguration?.AuthenticationOptions?.AuthenticationProviderKey) &&
!fileRoute.AuthenticationOptions.AllowAnonymousForGlobalAuthenticationOptions) ||
!string.IsNullOrEmpty(fileRoute.AuthenticationOptions?.AuthenticationProviderKey);
}
Comment on lines +29 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the body consists of one operator only, why not to define via expression body 👇

Suggested change
private static bool IsAuthenticated(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
{
return (!string.IsNullOrEmpty(globalConfiguration?.AuthenticationOptions?.AuthenticationProviderKey) &&
!fileRoute.AuthenticationOptions.AllowAnonymousForGlobalAuthenticationOptions) ||
!string.IsNullOrEmpty(fileRoute.AuthenticationOptions?.AuthenticationProviderKey);
}
private static bool IsAuthenticated(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
=> (!string.IsNullOrEmpty(globalConfiguration?.AuthenticationOptions?.AuthenticationProviderKey)
&& !fileRoute.AuthenticationOptions.AllowAnonymousForGlobalAuthenticationOptions)
|| !string.IsNullOrEmpty(fileRoute.AuthenticationOptions?.AuthenticationProviderKey);


private static bool IsAuthorized(FileRoute fileRoute) => fileRoute.RouteClaimsRequirement?.Count > 0;

Expand Down
4 changes: 2 additions & 2 deletions src/Ocelot/Configuration/Creator/RoutesCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ public List<Route> Create(FileConfiguration fileConfiguration)

private DownstreamRoute SetUpDownstreamRoute(FileRoute fileRoute, FileGlobalConfiguration globalConfiguration)
{
var fileRouteOptions = _fileRouteOptionsCreator.Create(fileRoute);
var fileRouteOptions = _fileRouteOptionsCreator.Create(fileRoute, globalConfiguration);

var requestIdKey = _requestIdKeyCreator.Create(fileRoute, globalConfiguration);

var routeKey = _routeKeyCreator.Create(fileRoute);

var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileRoute);

var authOptionsForRoute = _authOptionsCreator.Create(fileRoute);
var authOptionsForRoute = _authOptionsCreator.Create(fileRoute.AuthenticationOptions, globalConfiguration.AuthenticationOptions);

var claimsToHeaders = _claimsToThingCreator.Create(fileRoute.AddHeadersToRequest);

Expand Down
11 changes: 10 additions & 1 deletion src/Ocelot/Configuration/File/FileAuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ public FileAuthenticationOptions()
}

public string AuthenticationProviderKey { get; set; }
public List<string> AllowedScopes { get; set; }
public List<string> AllowedScopes { get; set; }

/// <summary>
/// Allows anonymous authentication globally.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It allows per route actually.

/// <para>The property is significant only if the global AuthenticationOptions are used.</para>
/// </summary>
/// <value>
/// <see langword="true"/> if it is allowed; otherwise, <see langword="false"/>.
/// </value>
public bool AllowAnonymousForGlobalAuthenticationOptions { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name could be shorter, like that: AllowAnonymousAuthentication or even AllowAnonymous.


public override string ToString()
{
Expand Down
3 changes: 3 additions & 0 deletions src/Ocelot/Configuration/File/FileGlobalConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public FileGlobalConfiguration()
LoadBalancerOptions = new FileLoadBalancerOptions();
QoSOptions = new FileQoSOptions();
HttpHandlerOptions = new FileHttpHandlerOptions();
AuthenticationOptions = new FileAuthenticationOptions();
}

public string RequestIdKey { get; set; }
Expand All @@ -28,5 +29,7 @@ public FileGlobalConfiguration()
public FileHttpHandlerOptions HttpHandlerOptions { get; set; }

public string DownstreamHttpVersion { get; set; }

public FileAuthenticationOptions AuthenticationOptions { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using FluentValidation;
using Microsoft.AspNetCore.Authentication;
using Ocelot.Configuration.File;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Ocelot.Configuration.Validator;

public class FileAuthenticationOptionsValidator : AbstractValidator<FileAuthenticationOptions>
{
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;

public FileAuthenticationOptionsValidator(IAuthenticationSchemeProvider authenticationSchemeProvider)
{
_authenticationSchemeProvider = authenticationSchemeProvider;

RuleFor(authOptions => authOptions.AuthenticationProviderKey)
.MustAsync(IsSupportedAuthenticationProviders)
.WithMessage("{PropertyName}: {PropertyValue} is unsupported authentication provider");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What placeholders are here?
Are they implicit? Where is definition of these variables?

}

private async Task<bool> IsSupportedAuthenticationProviders(string authenticationProviderKey, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(authenticationProviderKey))
{
return true;
}

var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();

var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList();

return supportedSchemes.Contains(authenticationProviderKey);
Comment on lines +32 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Suggested change
var supportedSchemes = schemes.Select(scheme => scheme.Name).ToList();
return supportedSchemes.Contains(authenticationProviderKey);
return schemes.Select(scheme => scheme.Name).Contains(authenticationProviderKey);

The goal is removal of ToList() which is redundant, because we can go with Enumerable.Contains<string>() without problems.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ namespace Ocelot.Configuration.Validator
{
public class FileGlobalConfigurationFluentValidator : AbstractValidator<FileGlobalConfiguration>
{
public FileGlobalConfigurationFluentValidator(FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator)
public FileGlobalConfigurationFluentValidator(FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator,
FileAuthenticationOptionsValidator fileAuthenticationOptionsValidator)
Comment on lines +8 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each param is located on own line.

Suggested change
public FileGlobalConfigurationFluentValidator(FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator,
FileAuthenticationOptionsValidator fileAuthenticationOptionsValidator)
public FileGlobalConfigurationFluentValidator(
FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator,
FileAuthenticationOptionsValidator fileAuthenticationOptionsValidator)

Or, Don't make new line. Actually parameter names could be shorter to be on the same line.

{
RuleFor(configuration => configuration.QoSOptions)
.SetValidator(fileQoSOptionsFluentValidator);

RuleFor(configuration => configuration.AuthenticationOptions)
.SetValidator(fileAuthenticationOptionsValidator);
}
}
}
31 changes: 7 additions & 24 deletions src/Ocelot/Configuration/Validator/RouteFluentValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
namespace Ocelot.Configuration.Validator
{
public class RouteFluentValidator : AbstractValidator<FileRoute>
{
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;

public RouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, HostAndPortValidator hostAndPortValidator, FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator)
{
_authenticationSchemeProvider = authenticationSchemeProvider;

{
public RouteFluentValidator(HostAndPortValidator hostAndPortValidator,
FileQoSOptionsFluentValidator fileQoSOptionsFluentValidator,
FileAuthenticationOptionsValidator fileAuthenticationOptionsValidator)
{
RuleFor(route => route.QoSOptions)
.SetValidator(fileQoSOptionsFluentValidator);

Expand Down Expand Up @@ -65,8 +63,7 @@ public RouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemePr
});

RuleFor(route => route.AuthenticationOptions)
.MustAsync(IsSupportedAuthenticationProviders)
.WithMessage("{PropertyName} {PropertyValue} is unsupported authentication provider");
.SetValidator(fileAuthenticationOptionsValidator);

When(route => string.IsNullOrEmpty(route.ServiceName), () =>
{
Expand All @@ -84,21 +81,7 @@ public RouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemePr
{
RuleFor(r => r.DownstreamHttpVersion).Matches("^[0-9]([.,][0-9]{1,1})?$");
});
}

private async Task<bool> IsSupportedAuthenticationProviders(FileAuthenticationOptions authenticationOptions, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(authenticationOptions.AuthenticationProviderKey))
{
return true;
}

var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();

var supportedSchemes = schemes.Select(scheme => scheme.Name);

return supportedSchemes.Contains(authenticationOptions.AuthenticationProviderKey);
}
}

private static bool IsValidPeriod(FileRateLimitRule rateLimitOptions)
{
Expand Down
1 change: 1 addition & 0 deletions src/Ocelot/DependencyInjection/OcelotBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo
Services.TryAddSingleton<RouteFluentValidator>();
Services.TryAddSingleton<FileGlobalConfigurationFluentValidator>();
Services.TryAddSingleton<FileQoSOptionsFluentValidator>();
Services.TryAddSingleton<FileAuthenticationOptionsValidator>();
Services.TryAddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
Services.TryAddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
Services.TryAddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
Expand Down
Loading