From 1154ae33b4c9eefb0849f4868368f8406618e4bc Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sat, 28 Sep 2019 21:30:35 -0500 Subject: [PATCH] More work; doesn't even build at this point Why isn't JWT documented on .NET Core..... :/ --- src/PrayerTracker/App.fs | 20 ++++----- src/PrayerTracker/Cookies.fs | 10 +++-- src/PrayerTracker/Extensions.fs | 7 ++++ src/PrayerTracker/PrayerTracker.fsproj | 3 +- src/PrayerTracker/SecurityMiddleware.fs | 54 +++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 src/PrayerTracker/SecurityMiddleware.fs diff --git a/src/PrayerTracker/App.fs b/src/PrayerTracker/App.fs index bd51801..7df2b0f 100644 --- a/src/PrayerTracker/App.fs +++ b/src/PrayerTracker/App.fs @@ -53,8 +53,9 @@ module Configure = .AddAntiforgery() .AddSingleton(SystemClock.Instance) |> ignore - let config = svc.BuildServiceProvider().GetRequiredService() + let config = svc.BuildServiceProvider().GetRequiredService() let authConfig = config.GetSection "Tokens" + let iss = authConfig.["Issuer"] svc.AddAuthentication() .AddCookie( fun opts -> @@ -62,17 +63,16 @@ module Configure = opts.Cookie.HttpOnly <- false opts.Cookie.SameSite <- SameSiteMode.Strict opts.SlidingExpiration <- true - opts.ClaimsIssuer <- authConfig.["Issuer"]) + opts.ClaimsIssuer <- iss) .AddJwtBearer( fun opts -> - opts.SaveToken <- true - opts.ClaimsIssuer <- "PrayerTracker" - opts.TokenValidationParameters <- TokenValidationParameters () - opts.TokenValidationParameters.ValidIssuer <- authConfig.["Issuer"] - opts.TokenValidationParameters.ValidAudience <- authConfig.["Issuer"] - opts.TokenValidationParameters.IssuerSigningKey <- SymmetricSecurityKey (Convert.FromBase64String authConfig.["Key"])) + opts.SaveToken <- true + opts.ClaimsIssuer <- iss + opts.TokenValidationParameters <- TokenValidationParameters ( + ValidIssuer = iss, + ValidAudience = iss, + IssuerSigningKey = SymmetricSecurityKey (Convert.FromBase64String authConfig.["Key"]))) |> ignore - let config = svc.BuildServiceProvider().GetRequiredService() let crypto = config.GetSection "CookieCrypto" CookieCrypto (crypto.["Key"], crypto.["IV"]) |> setCrypto svc.AddDbContext( @@ -195,7 +195,7 @@ module Configure = .UseStaticFiles() .UseSession() .UseRequestLocalization(app.ApplicationServices.GetService>().Value) - .UseAuthentication() + .UseSecurityMiddleware() .UseGiraffe(webApp) |> ignore Views.I18N.setUpFactories <| app.ApplicationServices.GetRequiredService () diff --git a/src/PrayerTracker/Cookies.fs b/src/PrayerTracker/Cookies.fs index 4a8cdaf..8c885d2 100644 --- a/src/PrayerTracker/Cookies.fs +++ b/src/PrayerTracker/Cookies.fs @@ -58,7 +58,9 @@ let setCrypto c = Crypto.crypto <- c /// Properties stored in the Small Group cookie type GroupCookie = - { /// The Id of the small group + { /// The token representing the group's claims + token : string + /// The Id of the small group [] GroupId : Guid /// The password hash of the small group @@ -100,8 +102,10 @@ type TimeoutCookie = /// The payload for the user's "Remember Me" cookie type UserCookie = - { /// The Id of the group into to which the user is logged - [< JsonProperty "g">] + { /// The token representing the current user's logged in claims + token : string + /// The Id of the group into to which the user is logged + [] GroupId : Guid /// The Id of the user [] diff --git a/src/PrayerTracker/Extensions.fs b/src/PrayerTracker/Extensions.fs index f8c93c7..d80521e 100644 --- a/src/PrayerTracker/Extensions.fs +++ b/src/PrayerTracker/Extensions.fs @@ -1,6 +1,7 @@ [] module PrayerTracker.Extensions +open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Http open Microsoft.FSharpLu open Newtonsoft.Json @@ -44,3 +45,9 @@ type ISession with type HttpContext with /// Get the EF database context from DI member this.dbContext () : AppDbContext = downcast this.RequestServices.GetService typeof + + +type IApplicationBuilder with + /// Insert the PrayerTracker custom security middleware into the request pipeline + member this.UseSecurityMiddleware () = + this.UseMiddleware(); diff --git a/src/PrayerTracker/PrayerTracker.fsproj b/src/PrayerTracker/PrayerTracker.fsproj index 085722f..4dc56f9 100644 --- a/src/PrayerTracker/PrayerTracker.fsproj +++ b/src/PrayerTracker/PrayerTracker.fsproj @@ -15,9 +15,10 @@ - + + diff --git a/src/PrayerTracker/SecurityMiddleware.fs b/src/PrayerTracker/SecurityMiddleware.fs new file mode 100644 index 0000000..9a01fa1 --- /dev/null +++ b/src/PrayerTracker/SecurityMiddleware.fs @@ -0,0 +1,54 @@ +namespace PrayerTracker + +open FSharp.Control.Tasks.V2.ContextInsensitive +open Microsoft.AspNetCore.Http +open Cookies +open System.IdentityModel.Tokens.Jwt +open System +open System.Security.Claims +open Microsoft.IdentityModel.Tokens + +/// Middleware to obtain the current user or group from a cookie or the authorization header +type SecurityMiddleware (next : RequestDelegate) = + + /// Try to get a JWT from the user's logged in cookie + let tryGetJwtFromUserCookie (ctx : HttpContext) = + match UserCookie.fromPayload ctx.Request.Cookies.[Key.Cookie.user] with + | Some cookie -> Some cookie.token + | None -> None + + /// Try to get a JWT from a group's logged in cookie + let tryGetJwtFromGroupCookie (ctx : HttpContext) = + match GroupCookie.fromPayload ctx.Request.Cookies.[Key.Cookie.group] with + | Some cookie -> Some cookie.token + | None -> None + + /// Try to get a JWT from the Authorization header + let tryGetJwtFromAuthHeader (ctx : HttpContext) = + match ctx.Request.Headers.["Authorization"] |> Seq.tryFind (fun x -> x.StartsWith "Bearer ") with + | Some hdr -> Some (hdr.Replace ("Bearer ", "")) + | None -> None + + /// Attempt to get the user either from a cookie or an Authorization header + member __.Invoke ctx = + task { + let jwt = + seq { + tryGetJwtFromUserCookie ctx + tryGetJwtFromGroupCookie ctx + tryGetJwtFromAuthHeader ctx + } + |> Seq.tryFind Option.isSome + |> Option.flatten + match jwt with + | Some token -> + let handler = JwtSecurityTokenHandler () + let mutable x : JwtSecurityToken ref = JwtSecurityToken () // does not build + let usr = handler.ValidateToken(token, TokenValidationParameters (), x) // JwtSecurityTokenHandler. ClaimsPrincipal () + ctx.User <- usr + () + | None -> () + do! next.Invoke ctx + } + +