Skip to content

Instantly share code, notes, and snippets.

@kspeakman
Last active April 13, 2017 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kspeakman/bd13d5b922a6abfcbb480a907161030c to your computer and use it in GitHub Desktop.
Save kspeakman/bd13d5b922a6abfcbb480a907161030c to your computer and use it in GitHub Desktop.
JWT expiration notification
-- Try to setup auto logout. But JWT parsing can fail in several ways.
--
-- Failure strategy:
-- 1. Console log failure.
-- 2. The fallback (not shown below) is to instruct the user to
-- manually log out and back in when we receive 401 Unauthorized.
--
-- TODO send failure to logging infrastructure.
notifyOnExpired : String -> Cmd Msg
notifyOnExpired token =
token
|> Jwt.onExpired Logout
|> Result.mapError (Debug.log "Auto-Logout could not be setup")
|> Result.withDefault Cmd.none
module Jwt exposing (JwtError(..), getExpiration, onExpired)
import String
import Base64
import Json.Decode as Json
import Time exposing (Time)
import Result
import Process
import Task
-- https://github.com/simonh1000/elm-jwt
-- used as a starter for this code
type JwtError
= InvalidJwtStructure
-- 3 sections required
| InvalidEncodingLength
-- only 1 or 2 padding characters are allowed in base 64
| Base64DecodingFailed String
| JsonDecodingFailed String
unurl : String -> String
unurl =
let
fix c =
case c of
'-' ->
'+'
'_' ->
'/'
c ->
c
in
String.map fix
fixlength : String -> Result JwtError String
fixlength s =
case String.length s % 4 of
0 ->
Result.Ok s
3 ->
Result.Ok <| String.concat [ s, "=" ]
2 ->
Result.Ok <| String.concat [ s, "==" ]
_ ->
Result.Err InvalidEncodingLength
expDecoder : Json.Decoder Float
expDecoder =
Json.field "exp" Json.float
getPayload : String -> Result JwtError String
getPayload token =
case token |> String.split "." of
header :: payload :: signature :: [] ->
payload
|> unurl
|> fixlength
_ ->
Result.Err InvalidJwtStructure
decodeBase64 : String -> Result JwtError String
decodeBase64 =
Base64.decode
>> Result.mapError Base64DecodingFailed
decodeExp : String -> Result JwtError Float
decodeExp =
Json.decodeString expDecoder
>> Result.mapError JsonDecodingFailed
-- TODO put somewhere else
(>>=) : Result x a -> (a -> Result x b) -> Result x b
(>>=) x f =
Result.andThen f x
-- TODO put somewhere else
(<$>) : Result x a -> (a -> b) -> Result x b
(<$>) x f =
Result.map f x
timeUntil : Time -> Time -> Time
timeUntil expiresAt now =
expiresAt - now
withMin : Time -> Time -> Time
withMin minTime expiresIn =
if expiresIn < minTime then
minTime
else
expiresIn
sendMsgAt : msg -> Time -> Cmd msg
sendMsgAt msg expiresAt =
Time.now
|> Task.map (timeUntil expiresAt >> withMin (1 * Time.second))
|> Task.andThen Process.sleep
|> Task.perform (always msg)
getExpiration : String -> Result JwtError Time
getExpiration token =
getPayload token
>>= decodeBase64
>>= decodeExp
<$> (*) Time.second
onExpired : msg -> String -> Result JwtError (Cmd msg)
onExpired msg token =
getExpiration token
<$> sendMsgAt msg
/* add this dependency to elm-package.json */
{
"dependencies": {
"truqu/elm-base64": "1.0.0 <= v < 2.0.0"
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment