Skip to content

Commit ef34123

Browse files
Merge pull request #358 from GridProtectionAlliance/simplify-authentication-handler
GSF-13 Do not use Microsoft.Owin.Security base classes for AuthenticationMiddleware
2 parents a09e390 + 4e6ec58 commit ef34123

File tree

3 files changed

+56
-37
lines changed

3 files changed

+56
-37
lines changed

Source/Libraries/GSF.Web/Security/AuthenticationHandler.cs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@
4141
using GSF.Reflection;
4242
using GSF.Security;
4343
using Microsoft.Owin;
44-
using Microsoft.Owin.Security;
45-
using Microsoft.Owin.Security.Infrastructure;
4644

4745
#pragma warning disable SG0015 // Validated - no hard-coded password present
4846

@@ -51,7 +49,7 @@ namespace GSF.Web.Security
5149
/// <summary>
5250
/// Handles authentication using the configured <see cref="ISecurityProvider"/> implementation in the Owin pipeline.
5351
/// </summary>
54-
public class AuthenticationHandler : AuthenticationHandler<AuthenticationOptions>
52+
public class AuthenticationHandler
5553
{
5654
#region [ Members ]
5755

@@ -67,11 +65,31 @@ innerException is AggregateException aggEx ?
6765
string.Join("; ", aggEx.Flatten().InnerExceptions.Select(inex => inex.Message)) :
6866
innerException.Message;
6967
}
70-
68+
7169
#endregion
72-
70+
71+
#region [ Constructors ]
72+
73+
/// <summary>
74+
/// Creates a new instance of the <see cref="AuthenticationHandler"/> class.
75+
/// </summary>
76+
/// <param name="context">Context of the request to be authenticated</param>
77+
/// <param name="options">Configuration options for the authentication handler</param>
78+
public AuthenticationHandler(IOwinContext context, AuthenticationOptions options)
79+
{
80+
Request = context.Request;
81+
Response = context.Response;
82+
Options = options;
83+
}
84+
85+
#endregion
86+
7387
#region [ Properties ]
7488

89+
private IOwinRequest Request { get; }
90+
private IOwinResponse Response { get; }
91+
private AuthenticationOptions Options { get; }
92+
7593
// Reads the authorization header value from the request
7694
private AuthenticationHeaderValue AuthorizationHeader
7795
{
@@ -104,6 +122,8 @@ private IPrincipal AnonymousPrincipal
104122
private string AuthTestPath =>
105123
Options.GetFullAuthTestPath("");
106124

125+
private bool Faulted { get; set; }
126+
107127
private string FaultReason { get; set; }
108128

109129
#endregion
@@ -112,10 +132,9 @@ private IPrincipal AnonymousPrincipal
112132

113133
/// <summary>
114134
/// The core authentication logic which must be provided by the handler. Will be invoked at most
115-
/// once per request. Do not call directly, call the wrapping Authenticate method instead.
135+
/// once per request.
116136
/// </summary>
117-
/// <returns>The ticket data provided by the authentication logic</returns>
118-
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
137+
public void Authenticate()
119138
{
120139
try
121140
{
@@ -125,7 +144,7 @@ protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
125144

126145
// No authentication required for anonymous resources
127146
if (Options.IsAnonymousResource(Request.Path.Value))
128-
return Task.FromResult<AuthenticationTicket>(null);
147+
return;
129148

130149
NameValueCollection queryParameters = System.Web.HttpUtility.ParseQueryString(Request.QueryString.Value);
131150

@@ -140,8 +159,7 @@ protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
140159
IIdentity logoutIdentity = new GenericIdentity(sessionID.ToString());
141160
string[] logoutRoles = { "logout" };
142161
Request.User = new GenericPrincipal(logoutIdentity, logoutRoles);
143-
144-
return Task.FromResult<AuthenticationTicket>(null);
162+
return;
145163
}
146164

147165
AuthenticationHeaderValue authorization = AuthorizationHeader;
@@ -214,32 +232,27 @@ Request.User is null ||
214232
else
215233
FaultReason = $"Authentication Pipeline Exception: {ex.Message}";
216234

217-
Log.Publish(MessageLevel.Warning, nameof(AuthenticateCoreAsync), FaultReason, exception: ex);
235+
Log.Publish(MessageLevel.Warning, nameof(Authenticate), FaultReason, exception: ex);
218236
}
219-
220-
return Task.FromResult<AuthenticationTicket>(null);
221237
}
222238

223239
/// <summary>
224-
/// Called once by common code after initialization. If an authentication middle-ware
225-
/// responds directly to specifically known paths it must override this virtual,
226-
/// compare the request path to it's known paths, provide any response information
227-
/// as appropriate, and true to stop further processing.
240+
/// Called once by common code after authentication to respond directly to specifically known paths.
228241
/// </summary>
229242
/// <returns>
230-
/// Returning false will cause the common code to call the next middle-ware in line.
231-
/// Returning true will cause the common code to begin the async completion journey
243+
/// Returning true will cause the common code to call the next middle-ware in line.
244+
/// Returning false will cause the common code to begin the async completion journey
232245
/// without calling the rest of the middle-ware pipeline.
233246
/// </returns>
234-
public override async Task<bool> InvokeAsync()
247+
public async Task<bool> AuthorizeAsync()
235248
{
236249
if (Faulted)
237250
{
238251
// Handle faulted authentication attempts to expose fault reason to client
239252
using TextWriter writer = new StreamWriter(Response.Body, Encoding.UTF8, 4096, true);
240253
await writer.WriteAsync(FaultReason);
241254
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
242-
return !HostingEnvironment.IsHosted;
255+
return HostingEnvironment.IsHosted;
243256
}
244257

245258
// Use Cases:
@@ -277,20 +290,20 @@ public override async Task<bool> InvokeAsync()
277290
cookieOptions.Path = Options.GetFullAuthTestPath(pathBase);
278291
Response.Cookies.Delete(Options.AuthenticationToken, cookieOptions);
279292

280-
return true; // Abort pipeline
293+
return false; // Abort pipeline
281294
}
282295

283296
// If the user is properly Authenticated but a redirect is requested send that redirect
284297
if (securityPrincipal?.Identity.IsAuthenticated == true && securityPrincipal.Identity.Provider.IsRedirectRequested)
285298
{
286299
Response.Redirect(securityPrincipal.Identity.Provider.RequestedRedirect ?? "/");
287-
return true;
300+
return false; // Abort pipeline
288301
}
289302

290303
// If request is for an anonymous resource or user is properly authenticated, allow
291304
// request to propagate through the Owin pipeline
292305
if (Options.IsAnonymousResource(urlPath) || securityPrincipal?.Identity.IsAuthenticated == true)
293-
return false; // Let pipeline continue
306+
return true; // Let pipeline continue
294307

295308
// Abort pipeline with appropriate response
296309
if (Options.IsAuthFailureRedirectResource(urlPath) && !IsAjaxCall() && !isAuthTest)
@@ -347,7 +360,7 @@ public override async Task<bool> InvokeAsync()
347360
string failureReason = SecurityPrincipal.GetFailureReasonPhrase(securityPrincipal, AuthorizationHeader?.Scheme, true);
348361
Log.Publish(MessageLevel.Info, "AuthenticationFailure", $"Failed to authenticate {currentIdentity} for {Request.Path}: {failureReason}");
349362

350-
return true; // Abort pipeline
363+
return false; // Abort pipeline
351364
}
352365

353366
private bool UserHasLogoutRole(IPrincipal user)

Source/Libraries/GSF.Web/Security/AuthenticationMiddleware.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,42 @@
2424
using System;
2525
using System.Linq;
2626
using System.Net;
27+
using System.Threading.Tasks;
2728
using System.Web.Hosting;
2829
using GSF.Diagnostics;
2930
using GSF.Security;
3031
using Microsoft.Owin;
31-
using Microsoft.Owin.Security.Infrastructure;
3232
using Owin;
3333

3434
namespace GSF.Web.Security
3535
{
3636
/// <summary>
3737
/// Middle-ware for configuring authentication using <see cref="ISecurityProvider"/> in the Owin pipeline.
3838
/// </summary>
39-
public class AuthenticationMiddleware : AuthenticationMiddleware<AuthenticationOptions>
39+
public class AuthenticationMiddleware : OwinMiddleware
4040
{
41+
private AuthenticationOptions Options { get; }
42+
4143
/// <summary>
4244
/// Creates a new instance of the <see cref="AuthenticationMiddleware"/> class.
4345
/// </summary>
4446
/// <param name="next">The next middle-ware object in the pipeline.</param>
4547
/// <param name="options">The options for authentication.</param>
4648
public AuthenticationMiddleware(OwinMiddleware next, AuthenticationOptions options)
47-
: base(next, options)
49+
: base(next)
4850
{
51+
Options = options;
4952
}
5053

51-
/// <summary>
52-
/// Returns the authentication handler that provides the authentication logic.
53-
/// </summary>
54-
/// <returns>The authentication handler to provide authentication logic.</returns>
55-
protected override AuthenticationHandler<AuthenticationOptions> CreateHandler() =>
56-
new AuthenticationHandler();
54+
/// <inheritdoc/>
55+
public override async Task Invoke(IOwinContext context)
56+
{
57+
AuthenticationHandler handler = new(context, Options);
58+
handler.Authenticate();
59+
60+
if (await handler.AuthorizeAsync())
61+
await Next.Invoke(context);
62+
}
5763
}
5864

5965
/// <summary>

Source/Libraries/GSF.Web/Security/AuthenticationOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace GSF.Web.Security
3232
/// <summary>
3333
/// Represents options for authentication using <see cref="AuthenticationHandler"/>.
3434
/// </summary>
35-
public sealed class AuthenticationOptions : Microsoft.Owin.Security.AuthenticationOptions
35+
public sealed class AuthenticationOptions
3636
{
3737
#region [ Members ]
3838

@@ -109,7 +109,7 @@ public sealed class AuthenticationOptions : Microsoft.Owin.Security.Authenticati
109109
/// <summary>
110110
/// Creates a new instance of the <see cref="AuthenticationOptions"/> class.
111111
/// </summary>
112-
public AuthenticationOptions() : base(SessionHandler.DefaultAuthenticationToken)
112+
public AuthenticationOptions()
113113
{
114114
m_authFailureRedirectResourceCache = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
115115
m_anonymousResourceCache = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

0 commit comments

Comments
 (0)