Following the release of the SAFE Stack v5 template, we have recently completed work on upgrading one of the demo applications that utilises the template, namely SAFE Bookstore.
Overview
This application uses a few features that are not included in the SAFE template and shows how easy it is to includes them.
Client
- Navigation
- Authorisation
Server
- Communicating with Azure storage
- Authentication
- Background services
Key features
Authentication and Authorisation
The application has a way to sign in and sign out which is using Saturn’s very simple to configure JWT token authentication. The token provided is used to
- Determine which routes the user can access on the Client
- Authenticate calls to the Fable Remoting configured endpoints for accessing the books Wishlist.
There are quite a few moving pieces to the authentication puzzle so take a look in the following files in the repository
- Server.fs
- Authorise.fs
- Index.fs
- Login.fs
Connection to storage account using Role Based Access Control
In development the Server connection to the Azure storage account uses a simple connection string. Ideally though in a deployed environment, we do not want to worry about connection strings and having to keep them secure.
For this we use Azure Role Based Access Control. When deploying the application using Farmer, we can instruct the deployment to set up the connection between the storage account and the server and assign access roles to the server. More information on the approach can be found here.
Build.fs
let web = webApp {
system_identity
setting "StorageAccountName" storageAccountName
//other settings ommitted
}
let storage = storageAccount {
grant_access web.SystemIdentity Roles.StorageTableDataContributor
grant_access web.SystemIdentity Roles.StorageBlobDataContributor
//other settings ommitted
}
Server.fs
//add the blob and table storage clients to the dependency injection container
services.AddAzureClients(fun builder ->
let blobStorage = "https://{storageAccountName}.blob.core.windows.net"
let tableStorage = "https://{storageAccountName}.table.core.windows.net"
builder.AddBlobServiceClient(blobStorage) |> ignore
builder.AddTableServiceClient(tableStorage) |> ignore
builder.UseCredential(DefaultAzureCredential()) |> ignore)
Scheduled background services using Quartz
Every 2 hours the server will reset the stored Wishlist back to a default set of books. For this we register a hosted service and use Quartz.net
Server.fs
services
.AddQuartz(fun config ->
let jobName = JobKey "reset-storage"
config
.AddJob<ResetStorageJob>(jobName)
.AddTrigger(fun trigger ->
trigger.ForJob(jobName)
.WithCronSchedule("0 0 */2 * * ?") |> ignore)
|> ignore)
.AddQuartzHostedService(fun options -> options.WaitForJobsToComplete <- true) |> ignore
ResetStorage.fs
type ResetStorageJob(blobService: BlobServiceClient, tableService: TableServiceClient) =
member this.BlobService = blobService
member this.TableService = tableService
interface IJob with
member this.Execute _ =
printfn $"Resetting wishlist to default"
resetStorage this.BlobService this.TableService
Summary
The SAFE Stack template can be built on to provide powerful functionality that can be used in deployed production applications.