Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/intricate/paseto-haskell
A Haskell implementation of PASETO (Platform-Agnostic Security Tokens).
https://github.com/intricate/paseto-haskell
cryptography haskell jwt paseto security token
Last synced: about 1 month ago
JSON representation
A Haskell implementation of PASETO (Platform-Agnostic Security Tokens).
- Host: GitHub
- URL: https://github.com/intricate/paseto-haskell
- Owner: intricate
- License: mit
- Created: 2024-09-16T23:57:34.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2024-09-28T19:09:13.000Z (about 2 months ago)
- Last Synced: 2024-09-30T09:02:54.127Z (about 2 months ago)
- Topics: cryptography, haskell, jwt, paseto, security, token
- Language: Haskell
- Homepage: https://hackage.haskell.org/package/paseto
- Size: 161 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# paseto-haskell
A Haskell implementation of
[PASETO (**P**latform-**A**gnostic **SE**curity **TO**kens)](https://paseto.io/).## What is PASETO?
PASETO is everything you love about JOSE (JWT, JWE, JWS) without any of the
[many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid).## Supported PASETO Versions
| | v1 | v2 | v3 | v4 |
| ---------| ---- | ---- | ---- | ---- |
| `local` | ❌ | ❌ | ✅ | ✅ |
| `public` | ❌ | ❌ | ✅ | ✅ |This library supports PASETO versions
[3](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/01-Protocol-Versions/Version3.md)
and
[4](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/01-Protocol-Versions/Version4.md)
along with both purposes (`local` and `public`).Since versions
[1](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/01-Protocol-Versions/Version1.md)
and
[2](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/01-Protocol-Versions/Version2.md)
are deprecated, there is no plan to support them.## Usage
For most use cases, it should be sufficient to import the `Crypto.Paseto`
module, which just re-exports types and functions from other modules
under `Crypto.Paseto.*`.However, there are some types and functions which aren't re-exported. So, if
you need access to any of those, you can import them from their respective
modules under `Crypto.Paseto.*`.### Generating keys
```haskell
-- Generate symmetric keys
symmetricKeyV3 <- generateSymmetricKeyV3
symmetricKeyV4 <- generateSymmetricKeyV4-- Generate signing keys
signingKeyV3 <- generateSigningKeyV3
signingKeyV4 <- generateSigningKeyV4-- Construct verification keys from signing keys
verificationKeyV3 <- fromSigningKey signingKeyV3
verificationKeyV4 <- fromSigningKey signingKeyV4
```### Building tokens
Example of building a V3 public PASETO token with some default claims:
```haskell
-- Read the signing key from a file
signingKeyBs <- BS.readFile "./signing-key-v3.bin"
signingKey <-
case bytesToSigningKeyV3 signingKeyBs of
Left err -> error "invalid signing key"
Right key -> pure key-- Get some default parameters for building the token
defaultParams <- getDefaultBuildTokenParams-- Add a footer and implicit assertion to the build parameters
let params =
defaultParams
{ btpFooter = Footer "1337 footer"
, btpImplicitAssertion = ImplicitAssertion "1337 implicit assertion"
}-- Build the token
buildResult <- runExceptT (buildTokenV3Public params signingKey)case buildResult of
Left err -> error "failed to build token"
Right token -> ...
```With the default build parameters, this token will have the following claims:
- An `exp` claim of 1 hour from the current system time.
- An `iat` claim of the current system time.
- A `nbf` claim of the current system time.### Decoding tokens
Example of decoding a V4 local PASETO token:
```haskell
-- Get some default validation rules
defaultRules <- getDefaultValidationRules-- Add another validation rule to check that the token issuer is
-- "paragonie.com"
let rules :: [ValidationRules]
rules = issuedBy (Issuer "paragonie.com") : defaultRules-- Decode, cryptographically verify, and validate the token
let decodeResult =
decodeTokenV4Local
symmetricKey
rules
(Just $ Footer "footer")
Nothing -- no implicit assertion
tokenTxtcase decodeResult of
Left err -> error "invalid token"
Right ValidatedToken { vtToken, vtClaims } -> ...
```### Claims
The `Claims` container API is not re-exported from `Crypto.Paseto` since it
contains functions which may conflict with those in `Prelude` and other
container implementations such as `Data.Map`.So you'll need to import:
```haskell
-- It isn't necessary for this to be qualified; just a recommendation.
import qualified Crypto.Paseto.Token.Claims as Claims
```#### Constructing claims
```haskell
-- Empty collection of claims
Claims.empty-- Collection of claims consisting of a single element
Claims.singleton (IssuerClaim $ Issuer "paragonie.com")-- Constructing a collection of claims from a list
Claims.fromList
[ IssuerClaim (Issuer "paragonie.com")
, SubjectClaim (Subject "test")
, TokenIdentifierClaim (TokenIdentifier "87IFSGFgPNtQNNuw0AtuLttPYFfYwOkjhqdWcLoYQHvL")
]-- Inserting a claim into an existing collection of claims
Claims.insert (SubjectClaim $ Subject "subject") claims
```#### Querying claims
```haskell
-- For example, looking up the issuer claim
case Claims.lookupIssuer claims of
Nothing -> error "issuer claim does not exist"
Just issuer -> ...
```#### Custom claims
It's also possible to construct custom claims (i.e. claims that are not
[registered/reserved for use within PASETO](https://github.com/paseto-standard/paseto-spec/blob/af79f25908227555404e7462ccdd8ce106049469/docs/02-Implementation-Guide/04-Claims.md#registered-claims)).Note that it's acceptable to store any kind of JSON data within a custom
claim.```haskell
-- Construct the custom claim's key
customClaimKey <-
case mkUnregisteredClaimKey "customData" of
Nothing -> error "invalid custom claim key"
Just k -> pure k-- Construct the custom claim
let customClaim :: Claim
customClaim = CustomClaim customClaimKey (Aeson.String "customValue")-- Now you can utilize it like any other 'Claim'. For example:
let claims :: Claims
claims = Claims.singleton customClaim
```If you attempt to pass a registered/reserved claim key to
`mkUnregisteredClaimKey`, it will return `Nothing`:```haskell
-- For example, this will return 'Nothing':
mkUnregisteredClaimKey "iss"
```### Validation
As seen in the [token decoding example above](#decoding-tokens), you can
construct a list of recommended default validation rules using
`getDefaultValidationRules`. At the moment, the default rules check that:- The `exp` claim is not in the past.
- The `iat` claim is not in the future.
- The `nbf` claim is not in the future.There are also some other simple pre-defined rules that you can utilize. For
example:- `forAudience`
- `identifiedBy`
- `issuedBy`
- `notExpired`
- `subject`
- `validAt`If this is insufficient for your use case, you can also construct your own
custom validation rules:```haskell
let f :: Claims -> Either ValidationError ()
f claims =
if isSomethingValid claims
then Right ()
else Left (ValidationCustomError "something was invalid, bro")customRule :: ValidationRule
customRule = ValidationRule f
```