Closed mvsmal closed 5 years ago
Hello there Mikhail,
First of all, I want to thank you for the talk! I watched the questions section again, surely I expected some people to be confused about authorization and security but indeed there were just a lot of them and you still had the patience to explain everything.
As for resolving the authorization header on every request, this one was actually one of the "features" I removed from the last major release, along with many others (for example, retry logic) that should really be added by the developer using the library as needed.
Naturally, you will ask: "So how do you implement this with existing API?"
Resolving access token for authorization is itself a request that should be made before calling protocol functions. Let us assume the developer has such resolveAccessToken
function:
let resolveAccessToken : unit -> Async<string> = (* implementation *)
Notice that the type unit -> Async<string>
is more appropriate than just unit -> string
because otherwise the request will have to be blocking. Sometimes, a user needs a client secret and refresh tokens to resolve the access token, in which case the signature will be
let resolveAccessToken : (clientSecret: string) -> (refreshToken: string) -> Async<string> =
(* implementation *)
Are you starting to see why I removed this feature? there are too many possibilities that are application-specific and it is up to the user to decide.
Now, instead of creating a proxy for the protocol with a constant header for authorization, we will create a proxy factory that returns a new proxy after resolving the access token:
module Server
type IBookStore = { getByTitle: string -> Async<Book list> }
let resolveAccessToken : unit -> Async<string> = (* implementation *)
let createApiFromToken (accessToken: string) : IBookStore =
Remoting.createApi()
|> Remoting.withAuthorizationHeader accessToken
|> Remoting.buildProxy<IBookStore>
let createApi() : Async<IBookStore> =
async {
let! accessToken = resolveAccessToken()
return createApiFromToken accessToken
}
Now from application code:
let getBooksByTitle (title: string) =
async {
let! booksApi = Server.createApi()
return! booksApi.getByTitle title
}
Here we are creating the new proxy for the book store API every time we want to use the proxy, which is totally fine, proxy creation is a very cheap operation and it only occurs every once in a while.
So the added complexity is simply one extra cheap function call and you are there. Maybe I should add this to the documentation as well, every time I think I covered everything, I am wrong again :smile:
Lastly, the error on the build servers seem because the drivers do not have executable permissions, you can ignore this for now, I will take a look later when time permits.
Thank you for a great explanation, Zaid. After it we can close this PR, it really doesn't make sense. However, as you mentioned, it would be awesome to add this example to the documentation, since it was not that obvious for me at first.
Yes, I will definitely add more samples of code to the docs. Now that I think of it, it can still be done without an extra function call, something like this would work too I think:
let call (f: IBookStore -> Async<'T>) : Async<'T> =
async {
let! accessToken = resolveAccessToken()
let bookStore = createApiFromToken accessToken
return! f bookStore
}
Then use it as follows:
let getBooksByTitle (title: string) =
async {
let! books = Server.call (fun books -> books.getByTitle title)
return books
}
FP for the win! Just adding the sample code here so that I don't forget about it :smile: I will need to test this too to make sure everything work like one would expect.
I know that the recommended way for authentication/authorization in Fable.Remoting is to pass a token explicitly as a function argument. However after my talk about Fable.Remoting I got a lot of questions regarding this topic. So I decided that this functionality will be quite useful.
Currently you can add an Authorization header with
Remoting.withAuthorizationHeader
, but since it happens only once, the header must be constant. I addedRemoting.withAuthorizationHeaderResolver
function, which expects a function of typeunit -> string
to resolve the header on each request. This allows to get and apply the token dynamically with an ability to refresh it.