S
SolidJS•8mo ago
Shimano

Confused with SolidStart and context provider

Hello. First of all - complete noob in frontend dev. I have a problem with the context and context providers. I saw some basic tutorial for React and AuthContext - basically a context that contains a login, logout functions and hooks for the user and the user's token. I have adapted it for Solid (somewhat) and tried it in plain SolidJS - it worked fine. So I tried yoinking it to SolidStart with the same idea - wrap my app in an AuthProvider and call a useAuth hook where I need to do stuff with auth. Well the context is always undefined... Again, I am a complete noob and I may be doing things completely wrong. If you can help me or show me another way to solve my issue, I'd appreciate it. Will share code below.
16 Replies
Shimano
ShimanoOP•8mo ago
AuthProvider.tsx:
import { useNavigate } from "@solidjs/router";
import {
createContext,
createSignal,
useContext,
type ParentComponent,
type Accessor,
} from "solid-js";

type AuthContextT = {
user: Accessor<string | null>;
token: Accessor<string | null>;
logInFn: (username: string, password: string) => Promise<void>;
logOutFn: () => Promise<void>;
};

const AuthContext = createContext<AuthContextT>();

const AuthProvider: ParentComponent = ({ children }) => {
const navigate = useNavigate();

const [user, setUser] = createSignal<string | null>(null);
const [token, setToken] = createSignal<string | null>(null);

const logInFn = async (username: string, password: string) => {
setUser(username);
setToken("some token");
navigate("/");
return;
};

const logOutFn = async () => {
setUser(null);
setToken(null);
navigate("/login");
return;
};
return (
<AuthContext.Provider
value={{
user,
token,
logInFn,
logOutFn,
}}
>
{children}
</AuthContext.Provider>
);
};

const useAuth = (): AuthContextT => {
const providedAuthContext = useContext(AuthContext);
if (providedAuthContext === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return providedAuthContext;
};

export { AuthProvider, useAuth };
import { useNavigate } from "@solidjs/router";
import {
createContext,
createSignal,
useContext,
type ParentComponent,
type Accessor,
} from "solid-js";

type AuthContextT = {
user: Accessor<string | null>;
token: Accessor<string | null>;
logInFn: (username: string, password: string) => Promise<void>;
logOutFn: () => Promise<void>;
};

const AuthContext = createContext<AuthContextT>();

const AuthProvider: ParentComponent = ({ children }) => {
const navigate = useNavigate();

const [user, setUser] = createSignal<string | null>(null);
const [token, setToken] = createSignal<string | null>(null);

const logInFn = async (username: string, password: string) => {
setUser(username);
setToken("some token");
navigate("/");
return;
};

const logOutFn = async () => {
setUser(null);
setToken(null);
navigate("/login");
return;
};
return (
<AuthContext.Provider
value={{
user,
token,
logInFn,
logOutFn,
}}
>
{children}
</AuthContext.Provider>
);
};

const useAuth = (): AuthContextT => {
const providedAuthContext = useContext(AuthContext);
if (providedAuthContext === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return providedAuthContext;
};

export { AuthProvider, useAuth };
Usage in a component:
const LoginForm: Component = () => {
const auth = useAuth();
const loggedIn = auth.user() !== null;
// yada yada
return <></>
}

export default LoginForm;
const LoginForm: Component = () => {
const auth = useAuth();
const loggedIn = auth.user() !== null;
// yada yada
return <></>
}

export default LoginForm;
app.tsx:
// @refresh reload
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";
import NavBar from "./components/NavBar";
import { AuthProvider } from "./components/AuthProvider";

export default function App() {
return (
<Router
root={(props) => (
<AuthProvider>
<MetaProvider>
<NavBar />
<Title>App</Title>
<Suspense>{props.children}</Suspense>
</MetaProvider>
</AuthProvider>
)}
>
<FileRoutes />
</Router>
);
}
// @refresh reload
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";
import NavBar from "./components/NavBar";
import { AuthProvider } from "./components/AuthProvider";

export default function App() {
return (
<Router
root={(props) => (
<AuthProvider>
<MetaProvider>
<NavBar />
<Title>App</Title>
<Suspense>{props.children}</Suspense>
</MetaProvider>
</AuthProvider>
)}
>
<FileRoutes />
</Router>
);
}
REEEEE
REEEEE•8mo ago
Hiya, unlike in React, components in Solid don't rerun so that access to auth.user in LoginForm won't be reactive to changes to user. For things to be reactive, you'll have to use it in an effect, jsx, or memo Or make loggedIn into a function In which case, it will also work fine Oops
Shimano
ShimanoOP•8mo ago
Well it throws the error even when I just declare auth.
REEEEE
REEEEE•8mo ago
I also just saw Don't destrucure props Like you're doing in AuthProvider
Shimano
ShimanoOP•8mo ago
Umm, can you please elaborate? You mean in the component declaration? The children? Sorry for my noobiness... 😓
REEEEE
REEEEE•8mo ago
Yes, dont do { children }. Instead change it to props and then where you use children replace it with props.children
Shimano
ShimanoOP•8mo ago
Hmm, will try and I'll let you know... brb Oh. My. God. You are my hero... 😄 In desperation I've tried to work with sessions - it kinda works too but it won't redirect me using the throw redirect("/"). Dunno but I will try it with a provider instead of sessions. If you have an example how to use it with session, I would appreciate that very much aswell! Also I cannot find the docs for redirect anywhere... 🤔 Only in https://docs.solidjs.com/solid-start/advanced/session but I get that the docs are still beta...
REEEEE
REEEEE•8mo ago
I don't have an example at the moment but you can make another support post and someone else who knows might be able to assist. You can also search the discord as this is a common question
Shimano
ShimanoOP•8mo ago
Ok, thanks for your time and help! Have a good one!
peerreynders
peerreynders•8mo ago
Like this?
Shimano
ShimanoOP•8mo ago
Thanks!
peerreynders
peerreynders•8mo ago
The basic API is described here Points to note - getSession will create one if there isn't one which you may not be expecting. - Once the server starts to stream the response (which it is eager to do) during SSR updating the cookies is a no go. Depending on the circumstances sometimes deferStream: true on createAsync will help but often handling cookie/session changes inside request middleware is more resilient.
SolidStart Release Candidate Documentation
SolidStart Release Candidate Documentation
Early release documentation and resources for SolidStart Release Candidate
Advanced - h3
More utilities
peerreynders
peerreynders•8mo ago
Another potential pitfall: With CSR your app is "lord of the manor", with SSR it's just another tenant who is sharing the building. Example:
const MyContext = createContext("initial");
const MyContext = createContext("initial");
Using a module global to store the context value - in CSR - for information not used during SSR is OK; however if it is initialized with request specific information to support SSR it can't be in a module global because the same module is used by multiple concurrent requests. At that point it becomes necessary stuff the context value inside the request at least while the app exists on the server side. Yes, SSR adds complexity.
Shimano
ShimanoOP•8mo ago
I am afraid that I don't quite understand you - as I've said I am a noob. Would you recommend using sessions rather than context for auth? My idea was that I have a token and some user info stored in storage (I am using cookieStorage from solid-primitives). User tries to login, the server makes a request to a backend service (a simple REST API with JWT auth). If it succeedes - the creds are valid - a JWT is returned and thus saved into cookies. Logout is simple - auth cookies are wiped and the user is redirected to the login page. When an authorized request to the REST API needs to be made, I just get it from the auth provider - if there is a token stored - and call it with that. I haven't thought about token validity expiring midway - meaning the token is valid for the page load but then becomes expired and the call to the backend is made - will work on that later if it comes to it. I wanted to use SSR for isolating the REST API since even the "public APIs" could be abused and the REST API calling some external services could be abused. This way I have only the frontend server exposed - yes the user can still abuse it somewhat but not the API directly. I am planning to implement another auth method directly for the API using a special token and I will expose selected endpoints - all of which will be secured by the API token auth. And sorry if I am blabbing too much... 🫣
peerreynders
peerreynders•8mo ago
I'm not an expert on these matters and my personal bias is that JWT is popular because it's convenient to farm out authentication but it still requires deft handling JSON Web Tokens Suck … and they serve different use cases JWT vs Session Authentication I'm more familiar with session-based authentication but that really only makes sense if your server is the one serving all of your client's requests that require authentication. After successful authentication the server returns a cookie to the browser which the client's JavaScript can't even access (HttpOnly) On the next request the cookie is returned to the server. Typically in middleware if a protected (API) route is accessed the cookie is accessed and the ID contained therein is used to access the server's session storage to verify that authentication for that ID exists and hasn't expired yet. If there is a problem the response is blocked or redirected. So if that REST API with JWT already exists then session doesn't make much sense unless you plan to proxy everything through your server, likely increasing latency. If you are implementing the REST API with API Routes then sessions can work-keeping in mind that with Start you have the option of using server functions via actions.
Cisco DevNet
YouTube
JSON Web Tokens Suck - Randall Degges (DevNet Create 2018)
JSON Web Tokens (JWTs) are all the rage in the security world. They're becoming more and more ubiquitous in web authentication libraries, and are commonly used to store a user's identity information. In this talk, you'll learn why JWTs suck, and why you should never use them.
DEV Community
JWT vs Session Authentication
Authentication vs Authorization What exactly is authentication, and how does it differ...
SolidStart Release Candidate Documentation
SolidStart Release Candidate Documentation
Early release documentation and resources for SolidStart Release Candidate
MDN Web Docs
Set-Cookie - HTTP | MDN
The Set-Cookie HTTP response header is used to send a cookie from the server to the user agent, so that the user agent can send it back to the server later. To send multiple cookies, multiple Set-Cookie headers should be sent in the same response.
Shimano
ShimanoOP•8mo ago
Thank you very much for your explanation!
Want results from more Discord servers?
Add your server