SaturnFramework / Saturn

Opinionated, web development framework for F# which implements the server-side, functional MVC pattern
https://saturnframework.org
MIT License
714 stars 108 forks source link

Auth sample or extension using OpenIdConnect #139

Closed talbottmike closed 5 years ago

talbottmike commented 5 years ago

Would you be interested in a PR with an auth sample or additional extension using OpenIdConnect? I've been using it for connecting to Azure and was wondering if you might be interested in such. I tried to follow the pattern of your existing sample(s).

The current implementation has a dependency on the Microsoft.AspNetCore.Authentication.OpenIdConnect NuGet package.

Here is the implementation.

let private addCookie state (c : AuthenticationBuilder) = if not state.CookiesAlreadyAdded then c.AddCookie() |> ignore

type ApplicationBuilder with
    ///Enables OpenId authentication with custom configuration
    [<CustomOperation("use_open_id_auth_with_config")>]
    member __.UseOpenIdAuthWithConfig(state: ApplicationState, (config : Action<Authentication.OpenIdConnect.OpenIdConnectOptions>) ) =
      let middleware (app : IApplicationBuilder) =
        app.UseAuthentication()

      let service (s : IServiceCollection) =
        let c = s.AddAuthentication(fun cfg ->
          cfg.DefaultScheme <- CookieAuthenticationDefaults.AuthenticationScheme
          cfg.DefaultChallengeScheme <- OpenIdConnectDefaults.AuthenticationScheme
          cfg.DefaultSignInScheme <- CookieAuthenticationDefaults.AuthenticationScheme)
        addCookie state c
        c.AddOpenIdConnect("OpenIdConnect",config) |> ignore
        s

      { state with
          ServicesConfig = service::state.ServicesConfig
          AppConfigs = middleware::state.AppConfigs
          CookiesAlreadyAdded = true
      }

And here is a sample of the config and usage.

let openIdConfig = 
  let clientId = "client_id"
  let tenantId = "tenant_id"
  let authority = sprintf "https://login.microsoftonline.com/%s/" tenantId

  let fn = (fun (opt : Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions) ->
    opt.ClientId <- clientId
    opt.Authority <- authority
    opt.UseTokenLifetime <- true
    opt.CallbackPath <- Microsoft.AspNetCore.Http.PathString("/signin-oidc")    
    opt.ResponseType <- "id_token"
    )

  new Action<Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectOptions>(fn)

let app = application {
    url ("http://0.0.0.0:" + port.ToString() + "/")
    use_router appRouter
    memory_cache
    use_static publicPath    
    use_open_id_auth_with_config openIdConfig
    use_gzip    
}
Krzysztof-Cieslak commented 5 years ago

Hey, this looks good. I'd happy to include it as a "extension" library - take a look how Saturn.Extensions... projects are organised.

talbottmike commented 5 years ago

Thanks! I'll take a look and submit a PR.

gfritz commented 5 years ago

I used this sample in an in progress "for fun" application at my work, and this sample OpenID + Azure Active Directory authentication worked just fine! 🎉 I would happy to submit a PR if @talbottmike does not have the bandwidth.

The only problem I have is configuring CORS correctly for my Fable UI to use the Saturn API for Open ID auth, but I think that is more of a problem with me than with this Saturn sample. The Saturn auth portion is doing fine.