Experiments with React and login

tl;dr: Rik tries to work out a way to get a React context component involved with checking whether a user is logged in or out of an app, and has some modest success

Are you in or are you out?

Thare are many, many (many) methods to keep track of whether a user has logged into a website or app. Back in the pre-history of the world wide web the job was done mainly by cookies. Cookies have been abused in some very inventive ways over the years. Which is why cookies scare people. Which is why various governments and agencies have taken action against cookies - and why most new sites you visit will stick up a cookie banner when you land on them; some of those banners are better than others. None have yet managed to make the experience ‘fun’.

One of the major tasks that cookies performed (and still perform) is to keep tabs on whether a user has authenticated themselves on a website - usually by supplying a username and password in a form that gets sent back to the website to generate a user session or, sometimes, a more persistent and intimate relationship between user and website. Nowadays there’s other ways to achieve the same thing - OAuth, OpenID, JSON Web Tokens - each implemented in their own way by Google, Facebook, Microsoft, Twitter, etc, etc … (apologies - I got bored of hunting down and adding links).

I shouldn’t complain - it’s fixing this sort of thing that keeps us web developers rolling in the cash.

At the end of the day it all boils down to a user supplying data to a server which - after some consideration (one hopes!) - decides to believe the user is indeed who they claim to be and passes a super-secret token of some sort back to the user, who can then present it in subsequent calls to the server - for instance when they click the link to take them to the super-secret cabal-only pages on a website.

React and the Art of the login (and logout)

For the front end business of a React-based site, this means we need to know if the user has the right token to access a page or see a scrolling list of stuff. Because of the way React apps work, this can be a bit of a bugger to deal with: there will probably be a lot of components scattered all over the site which need to know that the user is properly authenticated before they reveal their secret content.

In my previous battles with React, this has meant storing the user’s authentication data high up in the component heirarchy - usually in the App component itself, then prop-chaining the data through a whole bunch of intermediate containers until it reaches the components that will use the data to make their decisions. Yes, I know I should Redux it all, but I’ve never got round to learning Redux. So shoot me.

But now React has a working context, I don’t need to learn how to do this stuff with Redux. I should be able to do it with a context Component. Which is what the code below attempts to do.

The codey bits

For this learning exercise I’m building up on the code I’ve developed over the past couple of blog posts. I’m ditching the HelloRik component for a new Welcome component, and exchanging my English and Abroad locales for more professional looking ‘en’ and ‘xp’ dictionaries - ‘xp’ is for Pirate: I like Pirates!

components/UserProvider.js
Most of the work takes place in my UserProvider context component, which is where I’ll start. As normal, I shall offer comments in the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Nothing changes in the import statements, except adding Moment to the mix
import React, { Component } from 'react';
import moment from 'moment';

const UserContext = React.createContext();

const UserConsumer = UserContext.Consumer;

class UserProvider extends Component {

constructor(props) {
super(props);

// I've added an extra attribute to the context's state - 'isLoggedIn'
// - whenever a request is made to update the state, I can do a check
// - to see if the data supplied proves the user is logged in
this.updateUser = (items) => {
console.log('updateUser', items);

let token = (items.token != null) ? items.token : this.state.token;
let expires = (items.tokenExpires != null) ? items.tokenExpires : this.state.tokenExpires;

this.setState({
key: (items.key != null) ? items.key : this.state.key,
name: (items.name != null) ? items.name : this.state.name,
email: (items.email != null) ? items.email : this.state.email,
locale: (items.locale != null) ? items.locale : this.state.locale,
token: token,
tokenExpires: expires,
isLoggedIn: this.checkUserLoggedIn(token, expires)
});
};

// I check the supplied data for login status here
// - assumes there will be a token string and a tokenExpires date-time string
this.checkUserLoggedIn = (token, expires) => {
console.log('checkUserLoggedIn', token, expires);

let flag = false;
if(moment().isBefore(expires)){
if(token.length){
flag = true;
}
}
return flag;
};

// No other changes made to this component
this.state = {
key: '',
name: '',
email: '',
locale: 'en',
token: '',
tokenExpires: '',
isLoggedIn: false,
updateUser: this.updateUser
};
}

render() {
console.log('rendering UserProvider');

return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
);
}
};

export {
UserProvider,
UserConsumer
};

components/App.js
I’ve updated the App component so it shows the new Welcome component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react';

import { UserProvider } from './UserContext';
import Welcome from './Welcome';

class App extends Component {

render() {
console.log('rendering App');
return (
<UserProvider>
<div className="App">
<Welcome />
</div>
</UserProvider>
);
}
}

export default App;

components/Welcome.js
The new Welcome component is not much different to the old HelloRik component; I’ve added login and logout buttons to the mix so I can mock logged-in and logged-out states.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import React, { Component } from 'react';

import { UserConsumer } from './UserContext';
import { addCopy } from './Copy';

import '../styles/Welcome.css';

class Welcome extends Component {

render() {
console.log('rendering Welcome');

let p = this.props;
let copy = p.copy['Welcome'];

// Using one UserConsumer to help deliver all the copy and buttons
return (
<UserConsumer>
{( { locale, updateUser, isLoggedIn } ) => (
<div className="Welcome">

<p>{copy.paragraph1(isLoggedIn)}</p>

<UpdateLocale
buttonCopy={copy.localeButton}
locale={locale}
updateUser={updateUser} />

{isLoggedIn
? <Logout
buttonCopy={copy.logoutButton}
updateUser={updateUser} />
: <Login
buttonCopy={copy.loginButton}
updateUser={updateUser} />
}
</div>
)}
</UserConsumer>
);
}
}

// Helper components - three of them this time. UpdateLocale does the en/xp swapping
class UpdateLocale extends Component {

render() {
console.log('rendering UpdateLocale');
let update = (e) => {
this.props.updateUser({
locale: (this.props.locale === 'en') ? 'xp' : 'en'
});
};

return (
<button onClick={update}>
{this.props.buttonCopy}
</button>
);
}
}

// Add data to the context state to mock logged-in status
class Login extends Component {

render() {
console.log('rendering Login');
let update = (e) => {
this.props.updateUser({
token: 'logged-in',
tokenExpires: '2040-01-01'
});
};

return (
<button onClick={update}>
{this.props.buttonCopy}
</button>
);
}
}

// Remove data from the context state to mock logged-out status
class Logout extends Component {

render() {
console.log('rendering Logout');
let update = (e) => {
this.props.updateUser({
token: '',
tokenExpires: ''
});
};

return (
<button onClick={update}>
{this.props.buttonCopy}
</button>
);
}
}

export default addCopy(Welcome);

styles/Welcome.css
The previous example videos were pathetic. Adding some styling to the Welcome component to prove I can make pretty things.

1
2
3
4
5
6
7
8
9
10
11
.Welcome {
font-size: 24px;
margin: 1em 0;
text-align: center;
}

.Welcome button {
margin: 0 0.5em;
width: 10em;
padding: 0.3em;
}

locales/en.js
The ‘en’ dictionary with a section for the Welcome component.

1
2
3
4
5
6
7
8
9
10
11
12
13
const en = {

Welcome: {
paragraph1: (isLoggedIn) => isLoggedIn ?
'Hi Rik - you are logged in' :
'Sorry Rik, you are logged out',
localeButton: 'Swap locale',
loginButton: 'Login',
logoutButton: 'Logout',
},
};

export default en;

locales/xp.js
Like I said, I like Pirates!

1
2
3
4
5
6
7
8
9
10
11
12
13
const xp = {

Welcome: {
paragraph1: (isLoggedIn) => isLoggedIn ?
'Ahoy, Rik - belay that sail!' :
'Ahoy, Rik - ye be carousin\' now!',
localeButton: 'Jump ship',
loginButton: 'Come aboard',
logoutButton: 'Go carousin\'',
},
};

export default xp;

So does it work?

It works for me!

… but I have no idea if this approach is robust enough to handle a busy, complex React site. The React context stuff is still very new, so it will be interesting to see what the React community come up with as they start moving away from the various Redux solutions already out there.

The next obvious step is to see if I can get this functionality working with React Router.

Also: I am not a React expert, and this is probably not the way to do authorization checks - I’m experimenting and learning as I go along. If people spot obvious flaws in my code then please don’t hesitate to let me know in the comments.