Safer Authentication with TypeScript
Exploring Authentication APIs with TypeScript on Cloudflare Workers
I’ve been going through the Auth0 tutorial on Cloudflare Workers and integrating it with my own codebase. One of the first things I noticed was how loose the use of types was in the example code. This isn’t necessarily bad, it’s a tutorial, it’s not written in TypeScript, and the audience is just wanting to get something to work. But to me authentication is mission critical, which means I want to be confident about my types.
Let’s look at one of the first functions they have you implement:
This piece of code is pretty neat. One thing I like about it is that it returns a tuple. The first thing in the tuple is whether it was authorized or not, and the second thing is either the authorization information or the redirect information. What this means is that we can guard against the first item in the tuple and then use the second item.
But, unfortunately, TypeScript gets something different when it infers types for this function:
Typescript thinks that this is an Array of either [boolean or an authorization object] OR it’s an Array of either [boolean or a redirect object]. That’s messy! It means we can’t be confident that the implementation is correct. It would be valid to write it with the boolean flipped around, or with the object on either side. It also means we can’t use the types of this function to be confident that we’re only using the return type in the right way. For example to use the authorization
object we will have to use a type assertion on it:
We have to do this because the type of data
is:
boolean | { authorization: any } | { redirectUrl: any }
If someone made a mistake with the if statement here the type assertion would prevent any type errors from being raised.
We can improve the situation by encoding information about the sorts of return values the authorize
function can have within the return type. We can do this by using literals in the type:
Now we know for sure that it is a tuple type, and the tuple has two possible configurations. If the first value is true then the second value contains an authorization
object. If the first value is false then the second value contains a redirectUrl object.
This is a lot neater! If we got the booleans wrong inside the implementation accidentally, we would get a type error. But if we use it the same way:
We still have to use a type assertion! Now data
is:
{ authorization: any } | { redirectUrl: any }
Which is narrower than before (it no longer has a boolean option) but not as narrow as it could be. This happens because we haven’t narrowed that the authorize result’s first item (authenticated)
is true
before we do the array destructuring. We could potentially solve that like this:
By putting the return value into result
temporarily we’re able to narrow the type of result
before we use it. This is basically everything we want, but we’re no longer getting syntactic use out of the tuple type returned from authorize
. So it’s probably time to switch to an object type. Let’s rephrase our original authorize
function:
Now instead of returning a tuple, it returns an object. We still use the same trick with the literals so that we can be confident about the resulting types. That means we can now use it like this:
And tada! That’s about as neat as I reckon we can get it. We still have to use a result
variable temporarily to make sure the type narrowing works, but it’s not as syntactically ugly. We now can have much more confidence that we’re using the right object properties, and that we’re not returning the wrong values from our function.
Thanks for reading! I’m working on building up content on this blog. I’ll be exploring ideas that interest me at the moment. Right now I’m doing a lot with Cloudflare Workers, TypeScript and Svelte so that’s the current theme. But also expect design explorations, web components, performance, and other front-endy stuff.
If you’re liking the vibe, please subscribe!