I wrote my own login

A token is a note the server signs and then forgets about, which is fine right up until somebody wants to log out.

#php #javascript

Last month I ran one command and got the login I’d spent most of last year building.

It was php artisan make:auth, in a fresh Laravel project. Registration, login, logout, a “remember me” checkbox, and a password reset that emails you a link. I hadn’t gotten to the password reset yet. Reading what it had generated took longer than generating it.

Full stack, technically

The project is a small social network built around polls, something I’m working on in my spare time. You post a question with a few answers, people vote, and then everybody argues about it in the comments. Small sites like that were a big part of growing up for me, the kind where you knew the regulars. I’ve wanted to build one for about as long as I’ve known how to build anything.

I started it on Node.js with MongoDB, and at the time that was an easy call. I already write JavaScript all day, most of it jQuery and RequireJS inside Magento. Writing it on the server too meant one language top to bottom. MongoDB stores documents that look exactly like the objects I was already passing around in the browser, which my front-end brain could follow without translating anything.

Minimalist, as advertised

Node gives you a server and npm gives you a package for every piece of one. I didn’t find anything that had already decided how the pieces fit together. Express, which is where every tutorial starts, calls itself minimalist and means it. You get routing and middleware. The users, the sessions, the sign-up form and the rest are yours to design. Passport, which is where every tutorial goes next, checks credentials and leaves the rest to you too. I spent an evening with Meteor, which does come with accounts built in, and backed out for reasons that seemed important at the time.

Leaving those decisions to you is a fair design choice, and I suspect plenty of people are happier for it. I just didn’t know enough about back ends to make the decisions it hands you, and I found that out one decision at a time.

Sign here

Every tutorial I found for logging in to a Node API used JSON Web Tokens, so I did too. The shape of it is small. When you log in, the server signs a token with a secret and hands it to you. After that the front end sends the token with every request, and the server checks the signature.

auth.js
const jwt = require('jsonwebtoken');

function signIn(user) {
  return jwt.sign(
    { id: user._id, username: user.username },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );
}

function requireUser(req, res, next) {
  const token = (req.headers.authorization || '').replace('Bearer ', '');

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Not signed in' });
  }
}
JavaScript

I’d pictured a token as something like a key the server kept a copy of. It’s closer to a signed note. The server writes it, signs it, and forgets it exists. Anyone holding it gets in until it expires, and the server only ever checks that the signature is its own.

That answered a lot of questions I hadn’t thought to ask.

The payload isn’t secret. A token is signed, not encrypted. You can paste any token into jwt.io and read it. I’d put the username in so the front end could show it without asking the server. Then I built a settings page where you could change your username, and for up to a week afterward the token went on telling the front end you were still the old one.

Logging out doesn’t exist on the server. The front end throws its copy of the token away, and a copy anywhere else keeps working until it expires. The fix everybody suggests is a list of revoked tokens that the server checks on every request. Somewhere in there I noticed that a list the server checks on every request is a session.

Then there’s where to keep it. In localStorage, any script on the page can read it. In a cookie, every form needs protection against cross-site request forgery, and at that point you’ve rebuilt a session cookie with more moving parts. A short expiry means people get logged out all the time. So you add a second, longer-lived token for getting new first ones, and a route for that, and somewhere to keep the second token.

None of that is a knock on JSON Web Tokens. They make a lot of sense when one service has to trust a token another one issued. I was one person with one server, and I’d picked the tool for a problem I didn’t have because it was the one in the tutorials.

PHP, of all things

Laravel is a PHP framework, and I already knew some PHP. I’d learned enough of it at work to put a field on a product page, and I’d poked at plenty of WordPress themes before that. The syntax wasn’t foreign, even though I’d never have called myself fluent. It comes with the batteries included. Authentication, sessions, email and the database layer all come decided, and the decisions are written down in the docs.

Laravel’s login is a session in a cookie, which is the thing I’d been rebuilding badly. Each form gets its forgery protection from one line, @csrf. Logging out actually logs you out.

When I wanted people to log in with a username instead of an email address, the controller’s part of the change was this.

LoginController.php
public function username()
{
    return 'username';
}
PHP

The login controller gets its behavior from a trait, and the trait asks that method which field to check. Override the method and you’ve changed the answer. The rest is a column in the users table and a field on the form. In the Node version that would have been a weekend, and I’d have gotten some part of it slightly wrong.

So that’s what a controller is

The bigger change wasn’t the login. Laravel expects the whole app to be arranged a certain way, and learning the arrangement taught me things I’d been nodding along to for years.

Route::resource('polls', 'PollController') registers seven routes in one line. index lists the polls, create shows the form for a new one, store saves it, show shows one, edit and update do the same for changes, and destroy deletes it. Each one is a method on the controller. I’d heard “REST” and “CRUD” plenty of times. This was the first time I could see the whole shape of one, with every verb and every URL where I’d go looking for it. Model, view and controller had been three boxes in a diagram. Now they’re folders I can open, and I know what goes in each.

It also moved me off MongoDB and onto MySQL, which I expected to hurt and didn’t. A social network is mostly things pointing at other things. Users have polls, polls have options and options have votes. In the Node version, “what has this person voted on” meant writing code to walk the documents. In Laravel it’s a relationship on the model.

Poll.php
public function votes()
{
    return $this->hasManyThrough('App\Vote', 'App\PollOption', 'poll_id', 'option_id');
}
PHP

A poll’s votes, through its options. Sarah Mei wrote the long version of this in 2013, about a social network too. I read it about a year later than would have helped.

Magento has controllers too

There’s a Controller folder in half the modules I’ve opened over the last two years, and until last month I couldn’t have told you what one was for. It’s the same idea with a lot more around it, including the dependency injection I’ve been taking on faith. I’m curious how much of that reads differently now.

I don’t think the Node version was wasted, either. I know what a session is because I spent months building something that wasn’t one, and otherwise I’d probably have taken the framework’s word for all of it.

If you’ve built auth on Node and it went fine, I’d honestly like to know what I was missing. My guess is that you knew which decisions to make before you started, which is the part Laravel did for me.

Read similar posts

View posts by tag

#accessibility #agency #ai #careers #cross-browser-compatibility #css #culture #debugging #documentation #ecommerce #forms #git #html #images #javascript #performance #php #privacy #remote-work #responsive #scss #security #seo #shopify #small-print #testing #tooling #typography #wordpress