Library for a Web App Authorization, login, token, securing requests, ...
Add following into paket.references
Alma.Authorization
First of all you need to have an Api (defined in Shared project of your SAFE app).
// Shared
open Alma.Authorization
type IMyApi = {
// Public actions
Login: Username * Password -> AsyncResult<User, string>
// Secured actions
LoadGenericData: SecureRequest<unit> -> SecuredAsyncResult<Data list, string>
LoadUserData: SecureRequest<unit> -> SecuredAsyncResult<Data list, string>
}Then implement your Api.
// Init context
let currentApplication = {
Authorization = {
CurrentApplication = currentApplication.Gui.Instance // CurrentApplication must be a Issuer and Audience for the validated token (use SessionJWT for this)
AuthorizedBy = currentApplication.TokenKey // AuthorizedBy is JWTKey used to authorize the token (read/validate current token)
KeyForRenewToken = currentApplication.TokenKey // This key will be used for renewed token (create/write new token)
}
}
// Server
type MyErrorMessageType = MyErrorMessageType of string
module Api =
open Shared
open Feather.ErrorHandling
open Alma.Authorization
open Alma.Authorization.Session
/// Helper operator, which allows the action authorization
let inline private (>?>) authorize action =
Authorize.authorizeAction
current.Authorization
MyErrorMessageType
logAuthorizationError
authorize
action
/// Helper operator, which allows you to access the Username from a sessionJWT
let (>?>>) authorize action =
Authorize.authorizeAction currentApplication.Authorization
MyErrorMessageType
logAuthorizationError
authorize
(Authorize.Action.RequestWithUsername action)
let api = {
//
// Public actions
//
Login = fun credentials -> asyncResult {
let! credentials =
credentials
|> Dto.Deserialize.credentials
|> AsyncResult.ofResult <@> (CredentialsError.format >> ErrorMessage)
let! user =
credentials
|> User.login // "transfer" credentials into a User
|> AsyncResult.ofResult <@> (UserError.format >> ErrorMessage)
return user |> Dto.Serialize.user
}
//
// Secured actions
//
// [..Authorization..] [... ... Api Endpoint function ... ...]
LoadGenericData = Authorize.withLogin >?> fun ( (* action parameters would go here *) ) -> asyncResult {
let! data =
Data.load () <@> (DataLoadError.format >> ErrorMessage)
return data |> List.map Dto.Serialize.data
}
// [..Authorization..] [... ... Api Endpoint function ... ...]
LoadUserData = Authorize.withLogin >?>> fun ((username: Username) (* action parameters would go here *) ) -> asyncResult {
let! data =
Data.loadForUser username <@> (DataLoadError.format >> ErrorMessage)
return data |> List.map Dto.Serialize.data
}
}For scenarios where you want to sign JWTs using an external service like HashiCorp Vault's Transit engine:
open Feather.ErrorHandling
open Alma.Authorization.JWT
// Create JWTKey with external signing
let jwtKey = JWTKey.External {
Algorithm = "RS256" // The JWT algorithm (RS256, ES256, etc.)
Sign = fun unsignedJwt -> asyncResult {
let! signature =
unsignedJwt
|> JWTPart.unsignedJWTValue
|> Vault.signJWT // call external API
return JWTPart.Signature signature
}
}
// Use it to create tokens
let! jwt =
JWT.create
(Issuer "my-app")
(Audience "my-api")
jwtKey
(GenericTokenData.TokenData tokenData)- Increment version in
Alma.Authorization.fsproj - Update
CHANGELOG.md - Commit new version and tag it
./build.sh build./build.sh -t tests