Reputation: 9559
When I do TypeScript:
let token = req.headers['x-access-token'] || req.headers['authorization'] as string;
I have the following error:
Argument of type 'string | string[]' is not assignable to parameter of type 'string'
Does anyone know what string | string[]
type is? I mean if I want to use logical 'or' of two strings in TypeScript. How to do it?
And how to cast string | string[]
to string
type?
Upvotes: 29
Views: 67184
Reputation: 1878
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"
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).
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)
The case of multiple tokens is more fuzzy, you should take an agreement with your intents:
token = token[0] || ''
and remove the else
in the subsequent else if
(becoming if (!token) ...
) - still viable but not really a clean solutionPratically 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.
Upvotes: 6
Reputation: 19441
I guess you are using node.js. In this case req.headers
is of type IncomingHttpHeaders
which has an index-signature of: [header: string]: string | string[] | undefined;
That means, that req.headers['whatever']
can be of type string
or string[]
(array of string) or undefined
req.headers['x-access-token']
has type string | string[] | undefined
req.headers['authorization'] as string
is of type string
token
is string | string[]
, because
string | string[]
or
will use the 2nd part which is of type string
Hint
instead of req.headers['authorization']
you can use req.headers.authorization
which is of type string | undefined
.
interface IncomingHttpHeaders {
..
'authorization'?: string;
..
[header: string]: string | string[] | undefined;
}
Details
Note: the answer of Adrian Brand is fine and you can use it as is. For the sake of completion I'll just show a detailed way how you could handle all cases and explain the types:
const tokenValue= req.headers['x-access-token'] || req.headers['authorization'];
tokenValue
is of type string | string[] | undefined
.
Note, that it can also be undefined
when none of the headers exist.
We could handle this case:
if (!tokenValue) throw Error('missing header')
After this check typescript is smart enough to know that tokenValue
is now of type string | string[]
if (Array.isArray(tokenValue)) {
// In this if branch the type of `tokenValue` is `string[]`
} else {
// In this if branch, the type of `tokenValue` is `string`
}
Upvotes: 17
Reputation: 958
This is because it's possible to have multiple of the same header.
Here I've returned either the header or if it's an array the first instance of that header.
const getHeader = (name) => Array.isArray(req.headers[name]) ? req.headers[name][0] : req.headers[name];
let token = getHeader('x-access-token') ?? getHeader('authorization');
Upvotes: 4
Reputation: 21658
Try
let token = (req.headers['x-access-token'] || req.headers['authorization']) as string;
The compiler thinks req.headers['some string'] is an array of string, when you cast one side of the or operator you get a type of string or array of string. So do the or on both of them and then coerce the result to be a string.
Upvotes: 27