@@ -29,43 +29,49 @@ account on the site. So, without further ado, let's dive into authentication.
29
29
((("passwords")))
30
30
Naturally we're not going to mess about with remembering passwords
31
31
ourselves--besides being 'so' '90s, secure storage of user passwords is a
32
- security nightmare we'd rather leave to someone else. We'll use something
33
- fun called passwordless auth instead.
32
+ security nightmare we'd rather leave to someone else.
33
+ We'll use something fun called passwordless auth instead.
34
34
35
- (If you 'insist' on storing your own passwords, Django's default auth
36
- module is ready and waiting for you. It's nice and straightforward, and I'll
37
- leave it to you to discover on your own.)
35
+ (If you 'insist' on storing your own passwords,
36
+ Django's default auth module is ready and waiting for you.
37
+ It's nice and straightforward, and I'll leave it to you to discover on your own.)
38
38
39
39
40
40
[role="pagebreak-before less_space"]
41
41
=== Passwordless Auth
42
42
43
+ //TODO: this is called "magic links" these days,
44
+ // this section probably needs an update
45
+
43
46
44
47
((("authentication", "passwordless")))
45
48
((("Oauth")))
46
49
((("Openid")))
47
50
What authentication system could we use to avoid storing passwords ourselves?
48
- Oauth? Openid? "Login with Facebook"? Ugh. For me those all have
49
- unacceptable creepy overtones; why should Google or Facebook know what sites
50
- you're logging into and when?
51
+ Oauth? Openid? "Login with Facebook"? Ugh.
52
+ For me those all have unacceptable creepy overtones;
53
+ why should Google or Facebook know what sites you're logging into and when?
51
54
52
55
In the first edition I used an experimental project called "Persona",
53
- cooked up by a some of the wonderful techno-hippy-idealists at Mozilla, but
54
- sadly that project was abandoned.
56
+ cooked up by a some of the wonderful techno-hippy-idealists at Mozilla,
57
+ but sadly that project was abandoned.
55
58
56
59
Instead I've found a fun approach to authentication that goes by the name
57
60
of "Passwordless", but you might call it "just use email".
58
61
59
- The system was invented by someone annoyed at having to create
60
- new passwords for so many websites, who found himself just using random,
61
- throwaway passwords, not even trying to remember them, and using the
62
- "forgot my password" feature whenever he needed to log in again. You can
62
+ The system was invented (or at least popularised) back in 2014
63
+ by someone annoyed at having to create new passwords for so many websites.
64
+ They found themselves just using random, throwaway passwords,
65
+ not even trying to remember them, and using the "forgot my password" feature
66
+ whenever he needed to log in again.
67
+ You can
63
68
https://medium.com/@ninjudd/passwords-are-obsolete-9ed56d483eb#.cx8iber30[read
64
69
all about it on Medium].
65
70
66
- The concept is: just use email to verify someone's identity. If you're
67
- going to have a "forgot my password" feature, then you're trusting email
68
- anyway, so why not just go the whole hog? Whenever someone wants to log in,
71
+ The concept is: just use email to verify someone's identity.
72
+ If you're going to have a "forgot my password" feature,
73
+ then you're trusting email anyway, so why not just go the whole hog?
74
+ Whenever someone wants to log in,
69
75
we generate a unique URL for them to use, email it to them, and they then
70
76
click through that to get into the site.
71
77
@@ -894,14 +900,20 @@ unless you absolutely must,
894
900
so a user model that records an email address and nothing else
895
901
sounds good to me!
896
902
897
- By now I'm sure you can manage to create the tests folder and its pass:[<em>__init__.py</em>],
898
- remove _tests.py_, and then add a _test_models.py_ to say :
903
+ Let's start straight away with a tests folder instead of _tests.py_
904
+ in this app :
899
905
906
+ [subs=""]
907
+ ----
908
+ $ <strong>rm src/accounts/tests.py</strong>
909
+ $ <strong>mkdir src/accounts/tests</strong>
910
+ $ <strong>touch src/accounts/tests/__init__.py</strong>
911
+ ----
900
912
901
- // TODO: l022 doesnt work with dofirst because it has two files in,
902
- // change to separate commits for __init__ and model or somefink
913
+ And now let's add add a _test_models.py_ to say:
903
914
904
- [role="sourcecode dofirst-ch18l022"]
915
+
916
+ [role="sourcecode"]
905
917
.src/accounts/tests/test_models.py (ch18l023)
906
918
====
907
919
[source,python]
@@ -922,6 +934,7 @@ class UserModelTest(TestCase):
922
934
923
935
That gives us an expected failure:
924
936
937
+ [role=""]
925
938
----
926
939
django.core.exceptions.ValidationError: {'password': ['This field cannot be
927
940
blank.'], 'username': ['This field cannot be blank.']}
@@ -937,6 +950,7 @@ Password? Username? Bah! How about this?
937
950
----
938
951
from django.db import models
939
952
953
+
940
954
class User(models.Model):
941
955
email = models.EmailField()
942
956
----
@@ -971,6 +985,7 @@ Now when we run our tests, Django complains
971
985
that our custom user model is missing a couple of bits of metadata:
972
986
973
987
988
+ [role="ignore-errors"]
974
989
[subs="specialcharacters,macros"]
975
990
----
976
991
$ pass:quotes[*python src/manage.py makemigrations*]
@@ -996,6 +1011,7 @@ Here you go:
996
1011
----
997
1012
class User(models.Model):
998
1013
email = models.EmailField()
1014
+
999
1015
REQUIRED_FIELDS = []
1000
1016
----
1001
1017
====
@@ -1023,7 +1039,7 @@ class User(models.Model):
1023
1039
email = models.EmailField()
1024
1040
1025
1041
REQUIRED_FIELDS = []
1026
- USERNAME_FIELD = ' email'
1042
+ USERNAME_FIELD = " email"
1027
1043
is_anonymous = False
1028
1044
is_authenticated = True
1029
1045
----
@@ -1033,6 +1049,7 @@ class User(models.Model):
1033
1049
And now we get a slightly different error:
1034
1050
1035
1051
1052
+ [role="ignore-errors"]
1036
1053
[subs="specialcharacters,macros"]
1037
1054
----
1038
1055
$ pass:quotes[*python src/manage.py makemigrations*]
@@ -1118,16 +1135,16 @@ it would be better to have a specific test:
1118
1135
[source,python]
1119
1136
----
1120
1137
def test_email_is_primary_key(self):
1121
- user = User(email=' [email protected] ' )
1122
- self.assertEqual(user.pk, ' [email protected] ' )
1138
+ user = User(email=" [email protected] " )
1139
+ self.assertEqual(user.pk, " [email protected] " )
1123
1140
----
1124
1141
====
1125
1142
1126
1143
It'll help us remember if we ever come back and look at the code again
1127
1144
in future:
1128
1145
1129
1146
----
1130
- self.assertEqual(user.pk, ' [email protected] ' )
1147
+ self.assertEqual(user.pk, " [email protected] " )
1131
1148
AssertionError: None != '[email protected] '
1132
1149
----
1133
1150
@@ -1240,7 +1257,7 @@ AttributeError: 'Token' object has no attribute 'uid'. Did you mean: 'id'?
1240
1257
1241
1258
Eventually you should get to this code...
1242
1259
1243
- [role="sourcecode"]
1260
+ [role="sourcecode dofirst-ch18l038-1 "]
1244
1261
.src/accounts/models.py (ch18l038)
1245
1262
====
1246
1263
[source,python]
@@ -1290,6 +1307,7 @@ And, perhaps with a bit more wrangling of migrations,
1290
1307
that should get us to passing tests:
1291
1308
1292
1309
1310
+ [role="dofirst-ch18l041"]
1293
1311
[subs="specialcharacters,quotes"]
1294
1312
----
1295
1313
$ *python src/manage.py test accounts*
0 commit comments