--- title: "Encoding/Decoding JSON Web Tokens (JWT) in R" date: "`r Sys.Date()`" output: html_document vignette: > %\VignetteIndexEntry{Encoding/Decoding JSON Web Tokens (JWT) in R} %\VignetteEngine{knitr::rmarkdown} \usepackage[utf8]{inputenc} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) knitr::opts_chunk$set(comment = "") ``` JavaScript Object Signing and Encryption (JOSE) consists of a set of specifications for encryption and signatures based on the popular JSON format. This is work in progress, the IETF [jose workgroup](https://datatracker.ietf.org/wg/jose/) usually has the latest information. - [RFC7515](https://datatracker.ietf.org/doc/html/rfc7515): JSON Web Signature (JWS) - [RFC7516](https://datatracker.ietf.org/doc/html/rfc7516): JSON Web Encryption (JWE) - [RFC7517](https://datatracker.ietf.org/doc/html/rfc7517): JSON Web Key (JWK) - [RFC7518](https://datatracker.ietf.org/doc/html/rfc7518): JSON Web Algorithms (JWA) - [RFC7519](https://datatracker.ietf.org/doc/html/rfc7519): JSON Web Token (JWT) The `jose` package implements some of these specifications, in particular for working with JSON web tokens and keys. ### JSON Web Token: HMAC tagging The most common use of JSON Web Tokens is combining a small payload (the 'claim') with a HMAC tag or RSA/ECDSA signature. See also [https://jwt.io](https://jwt.io) for short introduction. ```{r} library(openssl) library(jose) # Example payload claim <- jwt_claim(user = "jeroen", session_key = 123456) # Encode with hmac key <- charToRaw("SuperSecret") (jwt <- jwt_encode_hmac(claim, secret = key)) # Decode jwt_decode_hmac(jwt, secret = key) ``` The decoding errors if the tag verification fails. ```{r error=TRUE} # What happens if we decode with the wrong key jwt_decode_hmac(jwt, secret = raw()) ``` ### JSON Web Token: RSA/ECDSA signature Similarly, we can use an RSA or ECDSA key pair we to verify a signature from someone's public key. ```{r} # Generate ECDSA keypair key <- ec_keygen() pubkey <- as.list(key)$pubkey # Sign with the private key (jwt <- jwt_encode_sig(claim, key = key)) # Decode and verify using the public key jwt_decode_sig(jwt, pubkey = pubkey) ``` Again decoding will error if the signature verification fails. ```{r error = TRUE} wrong_key <- ec_keygen() jwt_decode_sig(jwt, pubkey = wrong_key) ``` The spec also describes methods for encrypting the payload, but this is currently not widely in use yet. ### Reserved jwt-claim names You can include custom fields in your jwt payload, but the spec names a few [registered claims](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1) that are reserved for specific uses. - `iss` (Issuer): the principal that issued the JWT. - `sub` (Subject): the principal that is the subject of the JWT. - `aud` (Audience): the recipients that the JWT is intended for. - `exp` (Expiration Time): the expiration time on or after which the JWT must not be accepted. - `nbf` (Not Before): the time before which the JWT must not be accepted. - `iat` (Issued At): the time at which the JWT was issued. - `jti` (JWT ID): a unique identifier for the JWT. Each of these are optional, by default only `iat` is set. The `jwt_claim()` function will automatically do basic validation when you set additional fields from this list. For any other fields you can use any value. For example: ```{r} # Note that this token expires in 1 hour! myclaim <- jwt_claim( iss = "My webapp", exp = Sys.time() + 3600, myfield = "Some application logic", customer = "a cow" ) (jwt <- jwt_encode_sig(myclaim, key = key)) ``` The decode functions will automatically verify that the token has not expired (with a 60s grace period to account for inaccurate clocks), and error otherwise: ```{r} jwt_decode_sig(jwt, pubkey = pubkey) ``` ### Where is the JSON The jwt payloads consists of a head, body and signature which are separated with a dot into a single string. Both the header and body are actually `base64url` encoded JSON objects. ```{r} (strings <- strsplit(jwt, ".", fixed = TRUE)[[1]]) cat(rawToChar(base64url_decode(strings[1]))) cat(rawToChar(base64url_decode(strings[2]))) ``` However you should never trust this information without verifying the signature. This is what the `jwt_decode` functions do for you.