Sessions
Lets see how some of the common authentication and authorization patterns work:
Authentication
We need to know who the user is. This is usually done by checking the request for information. The best way for the client and server to do is using cookies.
We can use the Request
object to access the Cookie
header. We can then parse the cookie header to get the cookie value for a specific cookie name, for eg. "session"
. We can then use the cookie value to identify the session.
Let's look at an example of how to use the cookie to identify the user. Imagine we are implementating a getUser
function that returns the user making the request.
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<void>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" }
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<void>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" }
We use a SessionStorage
to manage the session data on the server. We can create one using the various storage factories we export: createCookieSessionStorage
, createMemorySessionStorage
, createSessionStorage
.
/lib/session.ts
tsx
import { createCookieSessionStorage(alias) const createCookieSessionStorage: CreateCookieSessionStorageFunction
import createCookieSessionStorage
} from "solid-start";
const storageconst storage: SessionStorage
= createCookieSessionStorage(alias) createCookieSessionStorage(options?: CookieSessionStorageOptions | undefined): SessionStorage
import createCookieSessionStorage
({ cookieThe Cookie used to store the session data on the client, or options used
to automatically create one.
(property) CookieSessionStorageOptions.cookie?: Cookie | (CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions & {
name?: string | undefined;
}) | undefined
: { secureSpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute
}
. When truthy, the
`Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*Note* be careful when setting this to `true`, as compliant clients will
not send the cookie back to the server in the future if the browser does
not have an HTTPS connection.
(property) CookieSerializeOptions.secure?: boolean | undefined
: import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.PROD(property) ImportMetaEnv.PROD: boolean
, secretsAn array of secrets that may be used to sign/unsign the value of a cookie.
The array makes it easy to rotate secrets. New secrets should be added to
the beginning of the array. `cookie.serialize()` will always use the first
value in the array, but `cookie.parse()` may use any of them so that
cookies that were signed with older secrets still work.
(property) CookieSignatureOptions.secrets?: string[] | undefined
: [import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.VITE_SESSION_SECRET], sameSiteSpecifies the boolean or string to be the value for the
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute
}
.
- `true` will set the `SameSite` attribute to `Strict` for strict same
site enforcement.
- `false` will not set the `SameSite` attribute.
- `'lax'` will set the `SameSite` attribute to Lax for lax same site
enforcement.
- `'strict'` will set the `SameSite` attribute to Strict for strict same
site enforcement.
- `'none'` will set the SameSite attribute to None for an explicit
cross-site cookie.
More information about the different enforcement levels can be found in
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification
}
.
*note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
(property) CookieSerializeOptions.sameSite?: boolean | "lax" | "strict" | "none" | undefined
: "lax", pathSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute
}
.
By default, the path is considered the "default path".
(property) CookieSerializeOptions.path?: string | undefined
: "/", maxAgeSpecifies the number (in seconds) to be the value for the `Max-Age`
`Set-Cookie` attribute. The given number will be converted to an integer
by rounding down. By default, no maximum age is set.
*Note* the
{@link
https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification
}
states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
possible not all clients by obey this, so if both are set, they should
point to the same date and time.
(property) CookieSerializeOptions.maxAge?: number | undefined
: 60 * 60 * 24 * 30, // 30 days httpOnlySpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute
}
.
When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
default, the `HttpOnly` attribute is not set.
*Note* be careful when setting this to true, as compliant clients will
not allow client-side JavaScript to see the cookie in `document.cookie`.
(property) CookieSerializeOptions.httpOnly?: boolean | undefined
: true }
});
/lib/session.ts
tsx
import { createCookieSessionStorage(alias) const createCookieSessionStorage: CreateCookieSessionStorageFunction
import createCookieSessionStorage
} from "solid-start";
const storageconst storage: SessionStorage
= createCookieSessionStorage(alias) createCookieSessionStorage(options?: CookieSessionStorageOptions | undefined): SessionStorage
import createCookieSessionStorage
({ cookieThe Cookie used to store the session data on the client, or options used
to automatically create one.
(property) CookieSessionStorageOptions.cookie?: Cookie | (CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions & {
name?: string | undefined;
}) | undefined
: { secureSpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute
}
. When truthy, the
`Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*Note* be careful when setting this to `true`, as compliant clients will
not send the cookie back to the server in the future if the browser does
not have an HTTPS connection.
(property) CookieSerializeOptions.secure?: boolean | undefined
: import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.PROD(property) ImportMetaEnv.PROD: boolean
, secretsAn array of secrets that may be used to sign/unsign the value of a cookie.
The array makes it easy to rotate secrets. New secrets should be added to
the beginning of the array. `cookie.serialize()` will always use the first
value in the array, but `cookie.parse()` may use any of them so that
cookies that were signed with older secrets still work.
(property) CookieSignatureOptions.secrets?: string[] | undefined
: [import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.VITE_SESSION_SECRET], sameSiteSpecifies the boolean or string to be the value for the
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute
}
.
- `true` will set the `SameSite` attribute to `Strict` for strict same
site enforcement.
- `false` will not set the `SameSite` attribute.
- `'lax'` will set the `SameSite` attribute to Lax for lax same site
enforcement.
- `'strict'` will set the `SameSite` attribute to Strict for strict same
site enforcement.
- `'none'` will set the SameSite attribute to None for an explicit
cross-site cookie.
More information about the different enforcement levels can be found in
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification
}
.
*note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
(property) CookieSerializeOptions.sameSite?: boolean | "lax" | "strict" | "none" | undefined
: "lax", pathSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute
}
.
By default, the path is considered the "default path".
(property) CookieSerializeOptions.path?: string | undefined
: "/", maxAgeSpecifies the number (in seconds) to be the value for the `Max-Age`
`Set-Cookie` attribute. The given number will be converted to an integer
by rounding down. By default, no maximum age is set.
*Note* the
{@link
https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification
}
states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
possible not all clients by obey this, so if both are set, they should
point to the same date and time.
(property) CookieSerializeOptions.maxAge?: number | undefined
: 60 * 60 * 24 * 30, // 30 days httpOnlySpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute
}
.
When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
default, the `HttpOnly` attribute is not set.
*Note* be careful when setting this to true, as compliant clients will
not allow client-side JavaScript to see the cookie in `document.cookie`.
(property) CookieSerializeOptions.httpOnly?: boolean | undefined
: true }
});
The SessionStorage
can be passed the cookie to get the session data about the request. How the session data is stored and retrievd is up to the implementation of the SessionStorage
. It can either save all the state within the cookie
itself, which createCookieSessionStorage
does, or it can save the session data in a database, and the cookie merely contains a session id.
So, lets use this storage
to get the session data for the request:
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<void>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" const sessionconst session: Promise<Session>
= storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(cookie); }
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<void>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" const sessionconst session: Promise<Session>
= storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(cookie); }
Typically, we will have saved the userId
in the session. If we don't find it, that means that this was not a authenticated request. Our getUser
function returns a null
when it doesn't find a user. If we find a userId
, we can use that to get the user from the database:
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<User | null>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
): PromiseRepresents the completion of an asynchronous operation
interface Promise<T>
<User | null> { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(cookie); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId) return null; return await hogwartsconst hogwarts: {
getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[];
getUser(id: string): {
name: string;
id: string;
};
getHouseMaster(house: string): {
name: string;
house: string;
id: string;
};
}
.getUser(method) getUser(id: string): {
name: string;
id: string;
}
(userId); }
/lib/session.ts
tsx
export async function getUserfunction getUser(request: Request): Promise<User | null>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
): PromiseRepresents the completion of an asynchronous operation
interface Promise<T>
<User | null> { const cookie = request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie") ?? "" const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(cookie); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId) return null; return await hogwartsconst hogwarts: {
getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[];
getUser(id: string): {
name: string;
id: string;
};
getHouseMaster(house: string): {
name: string;
house: string;
id: string;
};
}
.getUser(method) getUser(id: string): {
name: string;
id: string;
}
(userId); }
This helper can be used in all kinds of situations wherever we want to authenticate the request. They can be used in server functions and API routes, as well the createServerData$
and createServerAction$
primitives.
Lets see how we can use this in a createServerData$
call to make sure that only authenticated users can access the data. If the user is not authenticated, we can redirect them to the login page:
/routes/api/[house]/admin.ts
tsx
import { createServerData$(alias) const createServerData$: {
<T, S = true>(fetcher: RouteDataFetcher<S, T>, options?: RouteDataOptions<undefined, S> | undefined): Resource<T | undefined>;
<T, S = true>(fetcher: RouteDataFetcher<...>, options: RouteDataOptions<...>): Resource<...>;
}
import createServerData$
, redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
} from "solid-start/server"; import { RouteDataArgs(alias) interface RouteDataArgs<T = unknown>
import RouteDataArgs
} from "solid-start";
export function routeDatafunction routeData({ params }: RouteDataArgs): Resource<{
students: {
name: string;
house: string;
year: string;
}[];
} | undefined>
({ params(parameter) params: Params
}: RouteDataArgs(alias) interface RouteDataArgs<T = unknown>
import RouteDataArgs
) { return createServerData$(alias) createServerData$<{
students: {
name: string;
house: string;
year: string;
}[];
}, string>(fetcher: RouteDataFetcher<string, {
students: {
name: string;
house: string;
year: string;
}[];
}>, options?: RouteDataOptions<undefined, string> | undefined): Resource<...> (+1 overload)
import createServerData$
( async (house(parameter) house: string
, event(parameter) event: RouteDataEvent
) => { const user = await getUserfunction getUser(request: Request): Promise<User | null>
(event(parameter) event: RouteDataEvent
.request(property) FetchEvent.request: Request
); if (!user) throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login"); return {
students(property) students: {
name: string;
house: string;
year: string;
}[]
: hogwartsconst hogwarts: {
getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[];
getUser(id: string): {
name: string;
id: string;
};
getHouseMaster(house: string): {
name: string;
house: string;
id: string;
};
}
.getStudents(method) getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[]
(house(parameter) house: string
, "*"), };
},
{ key(property) key?: RouteDataSource<string>
: () => params(parameter) params: Params
.house } );
}
/routes/api/[house]/admin.ts
tsx
import { createServerData$(alias) const createServerData$: {
<T, S = true>(fetcher: RouteDataFetcher<S, T>, options?: RouteDataOptions<undefined, S> | undefined): Resource<T | undefined>;
<T, S = true>(fetcher: RouteDataFetcher<...>, options: RouteDataOptions<...>): Resource<...>;
}
import createServerData$
, redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
} from "solid-start/server"; import { RouteDataArgs(alias) interface RouteDataArgs<T = unknown>
import RouteDataArgs
} from "solid-start";
export function routeDatafunction routeData({ params }: RouteDataArgs): Resource<{
students: {
name: string;
house: string;
year: string;
}[];
} | undefined>
({ params(parameter) params: Params
}: RouteDataArgs(alias) interface RouteDataArgs<T = unknown>
import RouteDataArgs
) { return createServerData$(alias) createServerData$<{
students: {
name: string;
house: string;
year: string;
}[];
}, string>(fetcher: RouteDataFetcher<string, {
students: {
name: string;
house: string;
year: string;
}[];
}>, options?: RouteDataOptions<undefined, string> | undefined): Resource<...> (+1 overload)
import createServerData$
( async (house(parameter) house: string
, event(parameter) event: RouteDataEvent
) => { const user = await getUserfunction getUser(request: Request): Promise<User | null>
(event(parameter) event: RouteDataEvent
.request(property) FetchEvent.request: Request
); if (!user) throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login"); return {
students(property) students: {
name: string;
house: string;
year: string;
}[]
: hogwartsconst hogwarts: {
getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[];
getUser(id: string): {
name: string;
id: string;
};
getHouseMaster(house: string): {
name: string;
house: string;
id: string;
};
}
.getStudents(method) getStudents(house: string, year: string): {
name: string;
house: string;
year: string;
}[]
(house(parameter) house: string
, "*"), };
},
{ key(property) key?: RouteDataSource<string>
: () => params(parameter) params: Params
.house } );
}
/routes/session.server.ts
tsx
import { redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
} from "solid-start/server"; import { createCookieSessionStorage(alias) const createCookieSessionStorage: CreateCookieSessionStorageFunction
import createCookieSessionStorage
} from "solid-start/session"; };
type LoginFormtype LoginForm = {
username: string;
password: string;
}
= { username(property) username: string
: string; password(property) password: string
: string; };
export async function registerfunction register({ username, password }: LoginForm): Promise<any>
({ username(parameter) username: string
, password(parameter) password: string
}: LoginFormtype LoginForm = {
username: string;
password: string;
}
) { data(property) data: {
username: string;
password: string;
}
: { username(property) username: string
: username(parameter) username: string
, password(property) password: string
} });
}
export async function loginfunction login({ username, password }: LoginForm): Promise<any>
({ username(parameter) username: string
, password(parameter) password: string
}: LoginFormtype LoginForm = {
username: string;
password: string;
}
) { const user = await db.user.findUnique({ where(property) where: {
username: string;
}
: { username(property) username: string
} }); const isCorrectPasswordconst isCorrectPassword: boolean
= password(parameter) password: string
=== user.password; if (!isCorrectPasswordconst isCorrectPassword: boolean
) return null; }
const sessionSecret = import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.VITE_SESSION_SECRET;
const storageconst storage: SessionStorage
= createCookieSessionStorage(alias) createCookieSessionStorage(options?: CookieSessionStorageOptions | undefined): SessionStorage
import createCookieSessionStorage
({ cookieThe Cookie used to store the session data on the client, or options used
to automatically create one.
(property) CookieSessionStorageOptions.cookie?: Cookie | (CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions & {
name?: string | undefined;
}) | undefined
: { // secure doesn't work on localhost for Safari
// https://web.dev/when-to-use-local-https/
secureSpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute
}
. When truthy, the
`Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*Note* be careful when setting this to `true`, as compliant clients will
not send the cookie back to the server in the future if the browser does
not have an HTTPS connection.
(property) CookieSerializeOptions.secure?: boolean | undefined
: import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.PROD(property) ImportMetaEnv.PROD: boolean
, secretsAn array of secrets that may be used to sign/unsign the value of a cookie.
The array makes it easy to rotate secrets. New secrets should be added to
the beginning of the array. `cookie.serialize()` will always use the first
value in the array, but `cookie.parse()` may use any of them so that
cookies that were signed with older secrets still work.
(property) CookieSignatureOptions.secrets?: string[] | undefined
: ["hello"], sameSiteSpecifies the boolean or string to be the value for the
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute
}
.
- `true` will set the `SameSite` attribute to `Strict` for strict same
site enforcement.
- `false` will not set the `SameSite` attribute.
- `'lax'` will set the `SameSite` attribute to Lax for lax same site
enforcement.
- `'strict'` will set the `SameSite` attribute to Strict for strict same
site enforcement.
- `'none'` will set the SameSite attribute to None for an explicit
cross-site cookie.
More information about the different enforcement levels can be found in
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification
}
.
*note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
(property) CookieSerializeOptions.sameSite?: boolean | "lax" | "strict" | "none" | undefined
: "lax", pathSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute
}
.
By default, the path is considered the "default path".
(property) CookieSerializeOptions.path?: string | undefined
: "/", maxAgeSpecifies the number (in seconds) to be the value for the `Max-Age`
`Set-Cookie` attribute. The given number will be converted to an integer
by rounding down. By default, no maximum age is set.
*Note* the
{@link
https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification
}
states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
possible not all clients by obey this, so if both are set, they should
point to the same date and time.
(property) CookieSerializeOptions.maxAge?: number | undefined
: 60 * 60 * 24 * 30, httpOnlySpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute
}
.
When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
default, the `HttpOnly` attribute is not set.
*Note* be careful when setting this to true, as compliant clients will
not allow client-side JavaScript to see the cookie in `document.cookie`.
(property) CookieSerializeOptions.httpOnly?: boolean | undefined
: true }
});
export function getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { return storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie")); }
export async function getUserIdfunction getUserId(request: Request): Promise<string | null>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const session = await getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId || typeof userId !== "string") return null; }
export async function requireUserIdfunction requireUserId(request: Request, redirectTo?: string): Promise<string>
( request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
, redirectTo(parameter) redirectTo: string
: string = new URLThe URL interface represents an object providing static methods used for creating object URLs.
var URL: new (url: string | URL, base?: string | URL | undefined) => URL
(request(parameter) request: Request
.urlReturns the URL of request as a string.
(property) Request.url: string
).pathname(property) URL.pathname: string
) {
const session = await getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId || typeof userId !== "string") { const searchParamsconst searchParams: URLSearchParams
= new URLSearchParamsvar URLSearchParams: new (init?: string | string[][] | Record<string, string> | URLSearchParams | undefined) => URLSearchParams
([["redirectTo", redirectTo(parameter) redirectTo: string
]]); throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
(`/login?${searchParamsconst searchParams: URLSearchParams
}`); }
}
export async function getUserfunction getUser(request: Request): Promise<any>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const userIdconst userId: string | null
= await getUserIdfunction getUserId(request: Request): Promise<string | null>
(request(parameter) request: Request
); if (typeof userIdconst userId: string | null
!== "string") { return null;
}
try {
const user = await db.user.findUnique({ where(property) where: {
id: number;
}
: { id: NumberAn object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
var Number: NumberConstructor
(value?: any) => number
(userId) } }); } catch {
throw logoutfunction logout(request: Request): Promise<Response>
(request(parameter) request: Request
); }
}
export async function logoutfunction logout(request: Request): Promise<Response>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie")); return redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login", { headers(property) ResponseInit.headers?: HeadersInit | undefined
: { "Set-Cookie": await storageconst storage: SessionStorage
.destroySessionDeletes all data associated with the Session and returns the Set-Cookie
header to be used in the HTTP response.
(method) SessionStorage.destroySession(session: Session, options?: CookieSerializeOptions | undefined): Promise<string>
(session) }
});
}
export async function createUserSessionfunction createUserSession(userId: string, redirectTo: string): Promise<Response>
(userId(parameter) userId: string
: string, redirectTo(parameter) redirectTo: string
: string) { const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(); session.setSets a value in the session for the given `name`.
(method) Session.set(name: string, value: any): void
("userId", userId(parameter) userId: string
); return redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
(redirectTo(parameter) redirectTo: string
, { headers(property) ResponseInit.headers?: HeadersInit | undefined
: { "Set-Cookie": await storageconst storage: SessionStorage
.commitSessionStores all data in the Session and returns the Set-Cookie header to be
used in the HTTP response.
(method) SessionStorage.commitSession(session: Session, options?: CookieSerializeOptions | undefined): Promise<string>
(session) }
});
}
/routes/session.server.ts
tsx
import { redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) function redirect(url: string, init?: number | ResponseInit): Response
import redirect
} from "solid-start/server"; import { createCookieSessionStorage(alias) const createCookieSessionStorage: CreateCookieSessionStorageFunction
import createCookieSessionStorage
} from "solid-start/session"; };
type LoginFormtype LoginForm = {
username: string;
password: string;
}
= { username(property) username: string
: string; password(property) password: string
: string; };
export async function registerfunction register({ username, password }: LoginForm): Promise<any>
({ username(parameter) username: string
, password(parameter) password: string
}: LoginFormtype LoginForm = {
username: string;
password: string;
}
) { data(property) data: {
username: string;
password: string;
}
: { username(property) username: string
: username(parameter) username: string
, password(property) password: string
} });
}
export async function loginfunction login({ username, password }: LoginForm): Promise<any>
({ username(parameter) username: string
, password(parameter) password: string
}: LoginFormtype LoginForm = {
username: string;
password: string;
}
) { const user = await db.user.findUnique({ where(property) where: {
username: string;
}
: { username(property) username: string
} }); const isCorrectPasswordconst isCorrectPassword: boolean
= password(parameter) password: string
=== user.password; if (!isCorrectPasswordconst isCorrectPassword: boolean
) return null; }
const sessionSecret = import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.VITE_SESSION_SECRET;
const storageconst storage: SessionStorage
= createCookieSessionStorage(alias) createCookieSessionStorage(options?: CookieSessionStorageOptions | undefined): SessionStorage
import createCookieSessionStorage
({ cookieThe Cookie used to store the session data on the client, or options used
to automatically create one.
(property) CookieSessionStorageOptions.cookie?: Cookie | (CookieParseOptions & CookieSerializeOptions & CookieSignatureOptions & {
name?: string | undefined;
}) | undefined
: { // secure doesn't work on localhost for Safari
// https://web.dev/when-to-use-local-https/
secureSpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute
}
. When truthy, the
`Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set.
*Note* be careful when setting this to `true`, as compliant clients will
not send the cookie back to the server in the future if the browser does
not have an HTTPS connection.
(property) CookieSerializeOptions.secure?: boolean | undefined
: import.metaThe type of `import.meta`.
If you need to declare that a given property exists on `import.meta`,
this type may be augmented via interface merging.
.env(property) ImportMeta.env: ImportMetaEnv
.PROD(property) ImportMetaEnv.PROD: boolean
, secretsAn array of secrets that may be used to sign/unsign the value of a cookie.
The array makes it easy to rotate secrets. New secrets should be added to
the beginning of the array. `cookie.serialize()` will always use the first
value in the array, but `cookie.parse()` may use any of them so that
cookies that were signed with older secrets still work.
(property) CookieSignatureOptions.secrets?: string[] | undefined
: ["hello"], sameSiteSpecifies the boolean or string to be the value for the
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute
}
.
- `true` will set the `SameSite` attribute to `Strict` for strict same
site enforcement.
- `false` will not set the `SameSite` attribute.
- `'lax'` will set the `SameSite` attribute to Lax for lax same site
enforcement.
- `'strict'` will set the `SameSite` attribute to Strict for strict same
site enforcement.
- `'none'` will set the SameSite attribute to None for an explicit
cross-site cookie.
More information about the different enforcement levels can be found in
{@link
https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification
}
.
*note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
(property) CookieSerializeOptions.sameSite?: boolean | "lax" | "strict" | "none" | undefined
: "lax", pathSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute
}
.
By default, the path is considered the "default path".
(property) CookieSerializeOptions.path?: string | undefined
: "/", maxAgeSpecifies the number (in seconds) to be the value for the `Max-Age`
`Set-Cookie` attribute. The given number will be converted to an integer
by rounding down. By default, no maximum age is set.
*Note* the
{@link
https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification
}
states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is
possible not all clients by obey this, so if both are set, they should
point to the same date and time.
(property) CookieSerializeOptions.maxAge?: number | undefined
: 60 * 60 * 24 * 30, httpOnlySpecifies the boolean value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute
}
.
When truthy, the `HttpOnly` attribute is set, otherwise it is not. By
default, the `HttpOnly` attribute is not set.
*Note* be careful when setting this to true, as compliant clients will
not allow client-side JavaScript to see the cookie in `document.cookie`.
(property) CookieSerializeOptions.httpOnly?: boolean | undefined
: true }
});
export function getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { return storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie")); }
export async function getUserIdfunction getUserId(request: Request): Promise<string | null>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const session = await getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId || typeof userId !== "string") return null; }
export async function requireUserIdfunction requireUserId(request: Request, redirectTo?: string): Promise<string>
( request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
, redirectTo(parameter) redirectTo: string
: string = new URLThe URL interface represents an object providing static methods used for creating object URLs.
var URL: new (url: string | URL, base?: string | URL | undefined) => URL
(request(parameter) request: Request
.urlReturns the URL of request as a string.
(property) Request.url: string
).pathname(property) URL.pathname: string
) {
const session = await getUserSessionfunction getUserSession(request: Request): Promise<Session>
(request(parameter) request: Request
); const userId = session.getReturns the value for the given `name` in this session.
(method) Session.get(name: string): any
("userId"); if (!userId || typeof userId !== "string") { const searchParamsconst searchParams: URLSearchParams
= new URLSearchParamsvar URLSearchParams: new (init?: string | string[][] | Record<string, string> | URLSearchParams | undefined) => URLSearchParams
([["redirectTo", redirectTo(parameter) redirectTo: string
]]); throw redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
(`/login?${searchParamsconst searchParams: URLSearchParams
}`); }
}
export async function getUserfunction getUser(request: Request): Promise<any>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const userIdconst userId: string | null
= await getUserIdfunction getUserId(request: Request): Promise<string | null>
(request(parameter) request: Request
); if (typeof userIdconst userId: string | null
!== "string") { return null;
}
try {
const user = await db.user.findUnique({ where(property) where: {
id: number;
}
: { id: NumberAn object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
var Number: NumberConstructor
(value?: any) => number
(userId) } }); } catch {
throw logoutfunction logout(request: Request): Promise<Response>
(request(parameter) request: Request
); }
}
export async function logoutfunction logout(request: Request): Promise<Response>
(request(parameter) request: Request
: RequestThis Fetch API interface represents a resource request.
interface Request
) { const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(request(parameter) request: Request
.headersReturns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header.
(property) Request.headers: Headers
.get(method) Headers.get(name: string): string | null
("Cookie")); return redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
("/login", { headers(property) ResponseInit.headers?: HeadersInit | undefined
: { "Set-Cookie": await storageconst storage: SessionStorage
.destroySessionDeletes all data associated with the Session and returns the Set-Cookie
header to be used in the HTTP response.
(method) SessionStorage.destroySession(session: Session, options?: CookieSerializeOptions | undefined): Promise<string>
(session) }
});
}
export async function createUserSessionfunction createUserSession(userId: string, redirectTo: string): Promise<Response>
(userId(parameter) userId: string
: string, redirectTo(parameter) redirectTo: string
: string) { const session = await storageconst storage: SessionStorage
.getSessionParses a Cookie header from a HTTP request and returns the associated
Session. If there is no session associated with the cookie, this will
return a new Session with no data.
(method) SessionStorage.getSession(cookieHeader?: string | null | undefined, options?: CookieParseOptions | undefined): Promise<Session>
(); session.setSets a value in the session for the given `name`.
(method) Session.set(name: string, value: any): void
("userId", userId(parameter) userId: string
); return redirectA redirect response. Sets the status code and the `Location` header.
Defaults to "302 Found".
(alias) redirect(url: string, init?: number | ResponseInit): Response
import redirect
(redirectTo(parameter) redirectTo: string
, { headers(property) ResponseInit.headers?: HeadersInit | undefined
: { "Set-Cookie": await storageconst storage: SessionStorage
.commitSessionStores all data in the Session and returns the Set-Cookie header to be
used in the HTTP response.
(method) SessionStorage.commitSession(session: Session, options?: CookieSerializeOptions | undefined): Promise<string>
(session) }
});
}