TypeScript's type 'string | string[]' is not assignable to type 'string', what is the 'string | string[]' type? How to cast them to string?
Asked Answered
T

4

27

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?

Twotime answered 2/5, 2019 at 5:9 Comment(0)
F
17

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

  • the first part of your logical-or req.headers['x-access-token'] has type string | string[] | undefined
  • because of the cast expression the 2nd part req.headers['authorization'] as string is of type string
  • the type of token is string | string[], because
    • when the first part is defined, it can be string | string[]
    • when the first part is undefined, 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`
}
Fidelia answered 3/5, 2019 at 7:50 Comment(10)
This doesn't fix the issue but instead hides it. req.headers can have multiple values for each header. It's completely valid to pass multiple x-access-token headers for example. Please see my answer for an actual fix.Filtration
Incorrect. If I request a page and send two of the same header this code will break. If I sent two of the same header that value will no longer be a string but instead an array.Filtration
Nope that's also incorrect. Any client could do this.Filtration
Yow Bro you added a whole easse and didnt even give the answer. const head=req.headers["user-agent"] as string thats all you needBeheld
@Beheld Casting 'user-agent' to a string is wrong. The type of IncomingHttpHeaders['user-agent'] is string | undefined, and according to RFC723, sending the user-agent is optional. So, instead of casting to string, you should use a default: const userAgent = req.headers["user-agent"] || '';Fidelia
@Beheld And the question it is not about "user-agent". The OP also wants to get the value of x-access-token (which is not explicitly typed in IncomingHttpHeaders and thus of type string | string[] | undefined). i.e. at runtime you could get an array of string. And casting an array to a string will very likely to cause runtime errors, so it's better to explicitly handle that case.Fidelia
Question also do not say anything about x-access-token as well. Or any other header. It asks string[] | string Is not an string type which means an string Is the expected, so const name = whatEver as string quite all the dramaBeheld
@Beheld I guess, you didn't look at the code example in the question: req.headers['x-access-token'] || req.headers['authorization'] Fidelia
@Ernesto: here is a short example that should help you understand why casting string[] to string is a bad idea (click the Run button): typescript playgroundFidelia
@Beheld you're incorrect in this case bud. I'd highly suggest instead of making this into a fight about ego, etc you go and do a little bit more research on this topic. Casting an array to a string won't help the user at all in this case as the underlying type can be either an array, a string or undefined.Filtration
F
26

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.

Forsaken answered 2/5, 2019 at 5:14 Comment(1)
This answer is incomplete and unsafe, use TmTron's answer insteadWindblown
F
17

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

  • the first part of your logical-or req.headers['x-access-token'] has type string | string[] | undefined
  • because of the cast expression the 2nd part req.headers['authorization'] as string is of type string
  • the type of token is string | string[], because
    • when the first part is defined, it can be string | string[]
    • when the first part is undefined, 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`
}
Fidelia answered 3/5, 2019 at 7:50 Comment(10)
This doesn't fix the issue but instead hides it. req.headers can have multiple values for each header. It's completely valid to pass multiple x-access-token headers for example. Please see my answer for an actual fix.Filtration
Incorrect. If I request a page and send two of the same header this code will break. If I sent two of the same header that value will no longer be a string but instead an array.Filtration
Nope that's also incorrect. Any client could do this.Filtration
Yow Bro you added a whole easse and didnt even give the answer. const head=req.headers["user-agent"] as string thats all you needBeheld
@Beheld Casting 'user-agent' to a string is wrong. The type of IncomingHttpHeaders['user-agent'] is string | undefined, and according to RFC723, sending the user-agent is optional. So, instead of casting to string, you should use a default: const userAgent = req.headers["user-agent"] || '';Fidelia
@Beheld And the question it is not about "user-agent". The OP also wants to get the value of x-access-token (which is not explicitly typed in IncomingHttpHeaders and thus of type string | string[] | undefined). i.e. at runtime you could get an array of string. And casting an array to a string will very likely to cause runtime errors, so it's better to explicitly handle that case.Fidelia
Question also do not say anything about x-access-token as well. Or any other header. It asks string[] | string Is not an string type which means an string Is the expected, so const name = whatEver as string quite all the dramaBeheld
@Beheld I guess, you didn't look at the code example in the question: req.headers['x-access-token'] || req.headers['authorization'] Fidelia
@Ernesto: here is a short example that should help you understand why casting string[] to string is a bad idea (click the Run button): typescript playgroundFidelia
@Beheld you're incorrect in this case bud. I'd highly suggest instead of making this into a fight about ego, etc you go and do a little bit more research on this topic. Casting an array to a string won't help the user at all in this case as the underlying type can be either an array, a string or undefined.Filtration
A
6

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.

Azygous answered 10/2, 2022 at 10:40 Comment(0)
F
4

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');
Filtration answered 2/2, 2022 at 2:21 Comment(1)
Glad to see this was downvoted yet current is the only correct answer.Filtration

© 2022 - 2024 — McMap. All rights reserved.