I am using JWT(System.IdentityModel.Tokens.Jwt) for authentication in Giraffe F#. I am successfully getting the JWT token, but when I use it on my API/tables route I get a 401 unauthorized response. I have tried to change the issuer and audience to http://localhost:5001 and http://localhost:5000 respectively that didn't work then I disabled ValidateAudience but didn't work either.
Here is my route:
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Cors.Infrastructure
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open Giraffe
open backend.HttpHandlers
open System.Text
open Microsoft.IdentityModel.Tokens
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.AspNetCore.Http
open JwtAuth
let authorize : HttpFunc -> HttpContext -> HttpFuncResult =
requiresAuthentication (challenge JwtBearerDefaults.AuthenticationScheme)
let webApp =
choose [ subRoute
"/api"
(choose [ POST
>=> choose [ route "/token" >=> Auth.handlePostToken
route "/user/add" >=> handleAddUser
route "/table/create">=> handleAddTable
route "/table/addData">=> authorize>=> handleAddTableData ]
GET
>=> choose [
route "/tables">=> authorize>=> handleGetTableNames ] ])
setStatusCode 404 >=> text "Not Found" ]
// ---------------------------------
// Error handler
// ---------------------------------
let errorHandler (ex: Exception) (logger: ILogger) =
logger.LogError(ex, "An unhandled exception has occurred while executing the request.")
clearResponse
>=> setStatusCode 500
>=> text ex.Message
Here is my config:
// ---------------------------------
// Config and Main
// ---------------------------------
let configureCors (builder: CorsPolicyBuilder) =
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
|> ignore
let configureApp (app: IApplicationBuilder) =
let env =
app.ApplicationServices.GetService<IWebHostEnvironment>()
(match env.IsDevelopment() with
| true -> app.UseDeveloperExceptionPage()
| false ->
app.UseCors(configureCors)
.UseAuthentication()
.UseGiraffeErrorHandler(errorHandler)
.UseHttpsRedirection())
.UseGiraffe(webApp)
let configureServices (services: IServiceCollection) =
let sp = services.BuildServiceProvider()
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(fun options ->
options.TokenValidationParameters <-
TokenValidationParameters(
ValidateActor = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = "http://localhost:5001",
ValidAudience = "http://localhost:5000",
IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(Auth.secret))
))|> ignore
services.AddCors() |> ignore
services.AddGiraffe() |> ignore
let configureLogging (builder: ILoggingBuilder) =
builder.AddConsole().AddDebug() |> ignore
[<EntryPoint>]
let main args =
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webHostBuilder ->
webHostBuilder
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
|> ignore)
.Build()
.Run()
0
Here is the function I use to generate the token:
let secret = "spadR2dre#u-ruBrE#TepA&*Uf#U"
let generateToken email =
let claims = [|
Claim(JwtRegisteredClaimNames.Sub, email);
Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) |]
let expires = Nullable(DateTime.UtcNow.AddHours(1.0))
let notBefore = Nullable(DateTime.UtcNow)
let securityKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret))
let signingCredentials = SigningCredentials(key = securityKey, algorithm = SecurityAlgorithms.HmacSha256)
let token =
JwtSecurityToken(
issuer = "http://localhost:5001",
audience = "http://localhost:5000",
claims = claims,
expires = expires,
notBefore = notBefore,
signingCredentials = signingCredentials)
let tokenResult = {
Token = JwtSecurityTokenHandler().WriteToken(token)
}
tokenResult
I am using thunder client vscode to make the requests.
Related
I try to get some data from a grqphql endpoint with F#.
I use Fsharp.Data
let apiQuery = """query findData {
apiData(Model:{
PageNumber: 1,
PageSize: 100
})
{
ErrorMessage Success ValidationResult TotalCount
Data{
ItemId
}
}
}"""
let queryGraphQl () =
Http.RequestString
( apiUrl,
headers = [ ContentType HttpContentTypes.Json;
Authorization ("bearer " + token)
],
body =
TextRequest apiQuery
)
But I get (500) Internal Server Error
The same in Python works fine:
query_headers = {
"Authorization": 'bearer %s' % token,
'Content-Type': 'application/json'
}
response = requests.post(url, json={'query': apiQuery}, headers=query_headers)
Any suggestions what I am missing?
In Postman I have to add
Content-Length and Host like to be calculated when request is sent.
It appears that the F# and Python code is not equivalent. The Python code contains additional query keyword in the payload.
I don't know the specifics of your particular endpoint, but I wrote similar code using one of the public interfaces.
open System.Net
open FSharp.Data
open FSharp.Data.HttpRequestHeaders
let key = "********-****-****-****-*************"
let uri k = $"https://api.everbase.co/graphql?apikey={k}"
let gurl = uri key
let apiQuery = """{ "query" :
"{ client { ipAddress { country { name } city { name } } } }"
}"""
let connectToGraph apiUrl apiQuery =
try
let res = Http.RequestString( url = apiUrl, httpMethod="POST", body = TextRequest apiQuery, headers = [ ContentType HttpContentTypes.Json; UserAgent "mozilla" ])
res
with
| _ as ex -> ex.Message
[<EntryPoint>]
let main argv =
let res = connectToGraph gurl apiQuery
printf "Response: %A" res
0
I suppose you should separate the query in your F# code from the rest of the definition with a ':'. Also the actual payload should be wrapped in quotes/double quotes to form a valid Json value.
I would like to create a WebPart that forwards all requests to another web server that I specify.
Usage might look like this:
let app =
choose
[
path "/" >=> OK "Hello, world. "
path "/graphql" >=> createProxy "localhost:5000"
RequestErrors.NOT_FOUND "Not found"
]
startWebServer defaultConfig app
How should I implement this in Suave?
I found this snippet, but it seems to be for an old version of Suave:
let build_proxy_resolver (fwd_to_host : String) fwd_to_port =
let heserver = System.Net.Dns.GetHostEntry(fwd_to_host)
let ipaddr = heserver.AddressList.[0]
fun (request : HttpRequest) ->
Some (ipaddr, fwd_to_port)
let build_headers ctx =
//add and remove headers from the ctx, return the header list
ctx.request.headers
let proxy_app (ctx:HttpContext) =
let new_headers = build_headers ctx
let fwd_ctx = {ctx with request={ctx.request with headers=new_headers}}
let pxy = proxy (build_proxy_resolver "PROXY_TO.com" 80us) fwd_ctx
{ctx with response = { ctx.response with status=Suave.Types.Codes.HTTP_200; content=SocketTask pxy }} |> Some
This has now been added to Suave:
open System
open Suave
open Suave.Proxy
let app =
choose
[
path "/" >=> OK "Hello, world. "
path "/graphql" >=> proxy (Uri "http://localhost:5000")
RequestErrors.NOT_FOUND "Not found"
]
startWebServer defaultConfig app
module Main
open System
open System.Threading
open System.Threading.Tasks
open NetMQ
open NetMQ.Sockets
let uri = "ipc://hello-world"
let f (token : CancellationToken) =
use server = new ResponseSocket()
use poller = new NetMQPoller()
poller.Add(server)
printfn "Server is binding to: %s" uri
server.Bind(uri)
printfn <| "Done binding."
use __ = server.ReceiveReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
use __ = server.SendReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
poller.Run()
printfn "Server closing."
server.Unbind(uri)
let src = new CancellationTokenSource()
let token = src.Token
let task = Task.Run((fun () -> f token), token)
src.CancelAfter(100)
task.Wait() // Does not trigger.
My failed attempt looks something like this. The problem is that the poller will only check the cancellation token if it gets or sends a message. I guess one way to do it would be to send a special cancel message from the client rather than these tokens, but that would not work if the server gets into a send state.
What would be a reliable way of closing the server in NetMQ?
I have an existing owin application written in C# and would like to mount a suave application as a middleware but since I am relatively new to F# I am finding it quite difficult to navigate how this should be done. I think I'm looking for something like:
// in F# land
module MySuaveApp.ApiModule
let app =
choose
[ GET >=> choose
[ path "/hello" >=> OK "Hello GET"
path "/goodbye" >=> OK "Good bye GET" ]
POST >=> choose
[ path "/hello" >=> OK "Hello POST"
path "/goodbye" >=> OK "Good bye POST" ] ]
let getSuaveAsMiddleware() =
... magic goes here ...
// in Startup.cs
app.Use(MySuaveApp.ApiModule.getSuaveAsMiddleware())
As for what that magic should be I think it's a combination of OwinApp.ofAppFunc or OwinApp.ofMidFunc, but I can't for the life of me figure out what it should be.
There is no easy magic.1 ofAppFunc and ofMidFunc are here for creating WebParts out of OWIN components, i.e. OWIN -> Suave, whereas you want Suave -> OWIN.
The following works for your 'application' and serves as an example what would be needed to get it working:
open System.Runtime.CompilerServices
[<Extension>]
module Api =
open Suave
open Successful
open Filters
open Operators
open Microsoft.Owin
open System.Threading.Tasks
let app =
choose [ GET >=> choose [ path "/hello" >=> OK "Hello GET"
path "/goodbye" >=> OK "Good bye GET" ]
POST >=> choose [ path "/hello" >=> OK "Hello POST"
path "/goodbye" >=> OK "Good bye POST" ] ]
let withCtx (ctx : IOwinContext) webpart =
async {
let request =
{ HttpRequest.empty with
headers = ctx.Request.Headers |> List.ofSeq |> List.map (fun kvp -> kvp.Key, kvp.Value |> String.concat ",")
host = ctx.Request.Host.Value
``method`` = HttpMethod.parse ctx.Request.Method
url = ctx.Request.Uri }
let! res = webpart { HttpContext.empty with request = request }
res |> Option.iter (fun r ->
ctx.Response.StatusCode <- r.response.status.code
match r.response.content with
| Bytes bs -> ctx.Response.Write bs
| _ -> failwith "Not supported")
return res
}
type SuaveMiddleware(n) =
inherit OwinMiddleware(n)
override __.Invoke(context : IOwinContext) =
let res = withCtx context app |> Async.RunSynchronously
match res with
| Some _ -> Task.CompletedTask
| None -> base.Next.Invoke context
[<Extension>]
let UseSuave(app : Owin.IAppBuilder) =
app.Use(typeof<SuaveMiddleware>)
The main works is delegated to withCtx that tries to fulfill a request given a IOwinContext and a WebPart. It does so mainly by converting back and forth between Suave and OWIN context and related entities.
Note that this code is a PoC (Proof-of-Concept) and not fit for production.
The SuaveMiddleware forwards request to the next middleware if Suave cannot fulfill the request.
Using from C# is easy then:
using MySuave;
using Owin;
namespace Main
{
using System.Web.Http;
public class Startup
{
public static void Configuration(IAppBuilder appBuilder)
{
appBuilder.UseSuave();
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
appBuilder.UseWebApi(config);
}
}
}
given
namespace Main.Example
{
using System.Web.Http;
[RoutePrefix("api")]
public class ExampleController : ApiController
{
[HttpGet, Route("")]
public string Index()
{
return "Hello World";
}
}
}
And both URLs work:
http://localhost:9000/hello
Hello GET
http://localhost:9000/api
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Hello World</string>
1 At least none I know of. I'm happy to be proven wrong.
I am working on Suave 1.0 + Angular 2.0 sample app and very interesting to start Suave server in watch mode, so the server watch file changes (js,css,html) in root folder and sub-folders and automatically send refresh command to all open browser tabs with my application when any file is changed.
lite-server from Angular 2 5min Quckstark can do this and it is very handy.
I think that most of watch pieces can be found in latest Steffen Forkmann's post but it is not very clean how to send refresh to the open browser tabs.
Please provide complete code of similar implementation with Suave.
The code of Suave server should looks similar to this
#r "packages/Suave/lib/net40/suave.dll"
#r "packages/FAKE/tools/FakeLib.dll"
open Fake
open Suave
open Suave.Operators
open Suave.Sockets.Control
open Suave.WebSocket
open Suave.Utils
open Suave.Files
open Suave.RequestErrors
open Suave.Filters
open System
open System.Net
let port =
let rec findPort port =
let portIsTaken =
System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()
|> Seq.exists (fun x -> x.Port = int(port))
if portIsTaken then findPort (port + 1us) else port
findPort 8083us
let logger = Logging.Loggers.ConsoleWindowLogger Logging.LogLevel.Verbose
let refreshEvent = new Event<_>()
let handleWatcherEvents (events:FileChange seq) =
for e in events do
let fi = fileInfo e.FullPath
traceImportant <| sprintf "%s was changed." fi.Name
refreshEvent.Trigger()
let socketHandler (webSocket : WebSocket) =
fun cx -> socket {
while true do
let! refreshed =
Control.Async.AwaitEvent(refreshEvent.Publish)
|> Suave.Sockets.SocketOp.ofAsync
do! webSocket.send Text (ASCII.bytes "refreshed") true
}
let cfg =
{ defaultConfig with
homeFolder = Some (__SOURCE_DIRECTORY__)
bindings =
[ HttpBinding.mk HTTP IPAddress.Loopback port ]
listenTimeout = TimeSpan.FromMilliseconds 3000. }
let app : WebPart =
choose [
Filters.log logger logFormat >=> never
Filters.path "/websocket" >=> handShake socketHandler
Filters.GET >=> Filters.path "/" >=> file "index.html"
Writers.setHeader "Cache-Control" "no-cache, no-store, must-revalidate"
>=> Writers.setHeader "Pragma" "no-cache"
>=> Writers.setHeader "Expires" "0"
>=> browseHome
NOT_FOUND "Found no handlers."
]
let watcher =
!! ("app/*.js")
++ ("*.html")
|> WatchChanges handleWatcherEvents
try
System.Diagnostics.Process.Start(sprintf "http://localhost:%d/index.html" port) |> ignore
startWebServer cfg app
finally
watcher.Dispose()
So we setup watcher that handle changes in js(generated by TypeScript) and html files and send refresh command to the client, but in the same time we need to add following code to the head section of index.html to handle refresh on the client side
<!-- 3. Listen on refresh events from the server -->
<script language="javascript" type="text/javascript">
function init()
{
websocket = new WebSocket("ws://"+window.location.host+"/websocket");
websocket.onmessage = function(evt) { location.reload(); };
}
window.addEventListener("load", init, false);
</script>
The full demo app you can find here