Clarifications
Any one know what is 'string | string[]' type? I mean if I want use logical 'or' of two string in typescript. How to do it?
string | string[]
is a type union (TS Docs) which means the relative value can be either a string
OR string[]
(or Array<string>
or an array of strings).
The logical or operator ||
between two variables actually produces the union of the two variable types if and only if the left operand contains a falsish type (undefined
, null
, number
, string
and boolean
), otherwise produces the first variable type. The falsish type is actually configuration dependent (see note below real solution). Example:
type NonFalsishType = { prop: number };
let var1: number | undefined = 42;
let var2: string = 'var2'
let var3: NonFalsishType = { prop: 42 };
let test1: number | string = var1 || var2;
let test2: number | string = var2 || var1;
let test3: string | NonFalsishType = var2 || var3;
// strictNullCheck = true
// string type can be omitted because NonFalsishType
// is defenitely not falsy
let test4: NonFalsishType = var3 || var2;
// strictNullCheck = false
// string not omitted because null can be assigned to var3
let test4: NonFalsishType | string/* | null*/ = var3 || var2;
And How to cast 'string | string[]' type to string type?
The "casting" (the correct name is type assertion (TS Docs), it is a semantically different concept) can be done in different ways, the most common is achieved by using the as
keyword, but there is also the angle brackets notation:
// as
let myHeader: string = req.headers['authorization'] as string
// angle brackets
let myHeader: string = <string>req.headers['authorization']
Note: Type assertions do nothing at all during runtime, it will neither be compiled at all in JS code:
// TS
let myHeader: string = req.headers['authorization'] as string
// JS
var myHeader = req.headers['authorization'];
Type assertions are just ment to instruct the TS type checker to force a type to be restricted to another, ONLY during the type checking of the compilation phase. It is like saying to the compiler "I don't care which (of the union) type the variable actually is, treat it as
it would be of this specified type"
Possible Solution
Now the easisest solution is assert the string
type for your variable:
// parenthesis emphasize where the assertion is applied
let token: string = (req.headers['x-access-token'] as string) ||
(req.headers['authorization'] as string);
let token: string = (
req.headers['x-access-token'] ||
req.headers['authorization']
) as string;
// no runtime differences, just saying to the TS type checker
// two different way to see the same variables
This solution lead to different problems: what if the client sends to the server multiple x-access-token/authorization
headers?
You will end up with an array in the token variable, which means that your produced code could break (eg token.substr(10)
will produce a runtime error since arrays do not have substr
property, which strings have).
Even worse if the client does not send x-access-token/authorization
header at all (undefined
property will break the execution with any accessor).
Real solution
You need to think on what you need to achieve. The TypeScript type notations are not there just for decorate your code, but actually to produce a significant quality code through type and pattern checking. You should not ignore the fact that a variable can take multiple types, otherwise you will have bugs and security issues in a production environment.
If your real intent is to validate an access token you should be sure that the token is non-empty AND unique in order to identify an user:
// usually is a bad practice to authorize with multiple headers
// but it does not produce significant runtime error doing this
let token: string | string[] | undefined = req.headers['x-access-token'] || req.headers['authorization'];
if (typeof(token) === 'undefined') {
// no token passed in the header
// token variable is of type 'undefined' in this scope
// probably here, if the page require access, you should
// respond with a 401 unauth code
// you can skip this check by appending a check at
// the end of token variable initialization like this:
// let token: string | string[] = ... || '';
}
else if (Array.isArray(token)) {
// multiple tokens passed in the header
// token variable is of type 'string[]' in this scope
// for this special case see multiple tokens notes (at the end)
}
else if (!token) {
// the header contains the token but is actually an empty string
// token variable is of type 'string' in this scope
// same as undefined token, if the page require access, you should
// respond with a 401 unauth code
}
else {
// the header contains a non-empty string token
// token variable is of type 'string' also in this scope
// validate your token and respond by consequence (200 OK / 401 unath)
}
Note:
req.headers[key]
, as stated by @TmTron's answer, is of type string | string[] | undefined
, but undefined
is not mentioned in the union type in the error. This because it is possible to configure TypeScript (in the tsconfig.json
or by passing the proper command line argument) to ignore falsy values during the type checking (like false
, null
and undefined
). The option is strictNullCheck
(TS Docs), and by default is set to false
(meaning that TS will ignore the falsy values while type checking). If you put that option to true
the error would become:
Argument of type 'string | string[] | undefined' is not assignable to parameter of type 'string'
forcing you to take in account also the undefined
case (which in my experience usually prevents many and very many bugs)
Multiple Tokens Notes
The case of multiple tokens is more fuzzy, you should take an agreement
with your intents:
- always reject multiple tokens - best, suggested and common practice (401 unath)
- reject multiple tokens if they refer to different users - if there is the possibility, like ignore and drop elasped tokens but check they refer to the same user (validate token - 200 OK / 401 unath)
- accept as valid only the first token: just use
token = token[0] || ''
and remove the else
in the subsequent else if
(becoming if (!token) ...
) - still viable but not really a clean solution
Pratically there are some authentication techniques which make use of extended tokens (coma separated tokens), but are very scarce in the daily usage of security implementations.
Also note that teoretically a client should not send multiple headers with the same name, but actually a malicious user could simulate a call to your server with repeated headers in order to exploit some of the vulnerabilities of your application. And this is the reason why you should validate also the array case.