1Introduction to Wt::Auth
2========================
3
4Koen Deforche <koen@emweb.be> +
5
6For Wt 4.0.0 (September 19, 2017)
7
8:doc: link:http://www.webtoolkit.eu/wt/doc/reference/html/
9:tutorials: link:http://www.webtoolkit.eu/wt/doc/tutorial/
10:cpp: pass:[C++]
11
12== Prerequisites
13
14In this tutorial, we use an example as a hands-on introduction to the Wt authentication
15module. This example is included in the Wt distribution, in
16https://github.com/emweb/wt/tree/master/examples/feature/auth1[examples/feature/auth1].
17
18This introduction assumes that you have a reasonable understanding of
19Wt itself, in particular its stateful session model and the widget
20concept. If you haven't done so yet, you may want to go through the
21{tutorials}wt.html[Wt tutorial] first.
22
23== Introduction
24
25The authentication module implements the logic and widgets involved in
26getting users registered on your application, and letting them sign
27in. Note that this module is entirely optional, and simply implemented
28on top of http://www.webtoolkit.eu/wt[Wt].
29
30The module implements
31http://en.wikipedia.org/wiki/Authentication[authentication]. Its main
32purpose is to securely authenticate a user to sign in to your
33application. From your application, you will interact with the
34authentication module using a
35{doc}classWt_1_1Auth_1_1Login.html[Wt::Auth::Login] object, which you
36typically hold in your application object. It indicates the user
37currently signed in (if any), and propagates authentication events.
38
39How you use this information for
40http://en.wikipedia.org/wiki/Authorization[authorization] or to
41customize the user experience is out of the scope of the
42module. Because of Wt's built-in security features, with strong
43session hijacking mitigation, this is as straight forward as one can
44conceive it.
45
46Currently, the module provides the following features, which can be
47separately enabled, configured or customized:
48
49* *Password authentication*, using
50http://stackoverflow.com/questions/549/the-definitive-guide-to-forms-based-website-authentication[best
51practices] including http://en.wikipedia.org/wiki/Salted_hash[salted
52hashing] with strong cryptographic hash functions (such as
53http://en.wikipedia.org/wiki/Bcrypt[bcrypt]) and password strength
54checking.
55
56* *Remember-me* functionality, again using best practices, by
57associating authentication tokens stored in cookies to a user.
58
59* *Verified email addresses* using the typical confirmation email
60process.
61
62* *Lost password functionality* that uses the verified email address
63to prompt a user to enter a new password.
64
65* Authentication using *3rd party Identity Providers*, currently using
66http://oauth.net/2/[OAuth 2], with support for multiple identities per
67user. Currently, only Google and Facebook are supported for authentication,
68but Wt may support other OAuth 2 providers, like the standardized OpenIDConnect,
69in the future.
70
71* *Registration* logic, which includes also the logic needed to merge
72new (federated login) identities into existing user profiles. For
73example, if a user previously registered using a user name and
74password, he may later also authenticate using for example his Google
75Account and this new identity is added to his existing account.
76
77The logic for these features is implemented separately from the
78user interface components, which can be customized or completely
79replaced with your own widgets.
80
81Next to traditional password-based authentication, we've been careful
82to have a design that accommodates 3rd party identity providers
83(http://en.wikipedia.org/wiki/Federated_identity[federated login])
84such as Google, Twitter, Facebook, etc... and other authentication
85mechanisms in general (authenticating
86http://en.wikipedia.org/wiki/Reverse_proxy[reverse proxies], client
87SSL certificates, LDAP, token devices ...).
88
89////
90FIXME: update to the current state of affairs
91
92Federated login is a compelling proposition, but has currently not had
93widespread success, because of usability issues and other problems,
94including privacy concerns. There are a number of exciting
95developments however which promise to resolve this:
96
97* http://openid.net/connect/[OpenIDConnect] is the much plagued
98http://openid.net/[OpenID] protocol redone, but this time on top of
99http://oauth.net/2/[OAuth2.0], and aims to be the "open and
100distributed" alternative to
101http://developers.facebook.com/docs/guides/web/[Facebook Connect]. In
102particular it solves the confusion of using URLs as identity, and
103instead has moved on to email addresses. There is an auto-discovery
104protocol that queries your email provider, and this should eliminate
105most privacy concerns (because you do trust your email provider, don't
106you ?).
107
108* http://accountchooser.com/[AccountChooser] aims to provide a
109consistent and universal method for users to sign in to
110websites. Together with the use of the email address-as-identity this
111might just work.
112
113* Implementing a usable federated login system is complex, and
114increasingly so. Not only is the logic daunting
115(http://sites.google.com/site/oauthgoog/UXFedLogin/loginlogic[account
116linking] is one example), it also requires a good mix of Ajax-y
117user interface components, server-side logic and state with a protocol
118that involves receiving and sending HTTP requests, and integration
119into a traditional password-based authentication module. Well, we are
120solving that ;-)
121
122[NOTE]
123OAuth2.0, OpenIDConnect, and AccountChooser are currently draft
124specifications. We plan to expand our support as these drafts turn
125into endorsed specifications. In particular, it would be _a plan
126coming together_ when we add an +AccountChooserWidget+ that
127complements the current
128{doc}classWt_1_1Auth_1_1AuthWidget.html[Wt::Auth::AuthWidget]
129and
130{doc}classWt_1_1Auth_1_1RegistrationWidget.html[Wt::Auth::RegistrationWidget]
131classes and which allows users to authenticate with any email provider
132using the OpenIDConnect discovery protocol.
133////
134
135Obviously, the authentication logic needs to talk to a storage system,
136and it is designed to hook into a storage system using an abstract
137interface. A default implementation that leverages
138http://www.webtoolkit.eu/wt/doc/tutorial/dbo/tutorial.html[Wt::Dbo],
139Wt's ORM, is provided.
140
141== Module organization
142
143The following picture illustrates the main classes of the module.
144
145ifdef::basebackend-docbook[]
146image::img/auth.svg["Hello World",height=250,align="center"]
147endif::basebackend-docbook[]
148
149ifndef::basebackend-docbook[]
150image::img/auth.png[align="center"]
151endif::basebackend-docbook[]
152
153It uses a classical separation between Model classes and View classes
154(which are the widgets).
155
156There are three types of model classes:
157
158* *Service classes* are designed to be shared across all sessions
159(they do not have any state besides configuration). They contain logic
160which does not require transient state in a session.
161
162* *Session bound* model classes are usually kept in a session for the
163entire lifetime of a session (but don't need to be).
164
165* *Transient* model classes play an active role in the user-interface,
166and are instantiated in the context of certain view components. They
167implement logic which involves state while the user is progressing
168through the login and registration process.
169
170== Example
171
172We'll walk through a small example which is a basic application that
173uses the authentication module (included in the Wt distribution in
174https://github.com/kdeforche/wt/tree/master/examples/feature/auth1[examples/feature/auth1]). It
175is about 200 lines of C++ (which we'll discuss below), and has the
176following features:
177
178* Password-based authentication and registration
179* OAuth-2 login and registration, for Google and Facebook accounts
180* Password attempt throttling
181* Email verification and a lost password procedure
182* Remember-me tokens
183* And by virtue of Wt itself, falls back to plain HTML behavior if the
184browser does not support Ajax, strong security, spam resilience,
185etc...
186
187This example should help you to understand how to add authentication
188support to a new or existing Wt project.
189
190=== Setting up a user database
191
192We will be using the default implementation for an authentication
193database using +Wt::Dbo+, with the default persistence classes for
194authentication. This database implementation is found in
195{doc}classWt_1_1Auth_1_1Dbo_1_1UserDatabase.html[Wt::Auth::Dbo::UserDatabase],
196and it uses
197{doc}classWt_1_1Auth_1_1Dbo_1_1AuthInfo.html[Wt::Auth::Dbo::AuthInfo]
198as the persistence class for authentication information, which itself
199references two other persistence classes:
200
201* A user's *"identities"* are stored in a separate table. An identity
202uniquely identifies a user. Traditionally, a user would have only a
203single identity which is his login name (which could be his email
204address). But a user may accumulate more identities, corresponding to
205accounts with 3rd party identity providers. By allowing multiple
206identities, the user may identify using a choice of methods.
207* *Authentication tokens* are stored in a separate table. An
208authentication token usually corresponds to a "remember-me" cookie,
209and a user may have multiple "remember-me" cookies when using
210different computers.
211
212In addition, we define a +User+ type to which we can add the
213application data for a particular user (this could be address
214information, birth date, preferences, user role, etc...), and which we
215want to link up with the authentication system.
216
217The definition and persistence mapping for (a currently empty) User
218type is as given below:
219
220.User.h
221[source,cpp]
222----
223#include <Wt/Dbo/Types.h>
224#include <Wt/WGlobal.h>
225
226namespace dbo = Wt::Dbo;
227
228class User;
229using AuthInfo = Wt::Auth::Dbo::AuthInfo<User>;
230
231class User {
232public:
233  template<class Action>
234  void persist(Action& a)
235  {
236  }
237};
238
239DBO_EXTERN_TEMPLATES(User)
240----
241
242We declare a type alias for +AuthInfo+, which links the authentication
243information persistence class to our custom +User+ information
244persistence class.
245
246Next, we define a session class, which encapsulates the connection to
247the database to store authentication information, and which also
248tracks the user currently logged in, in a web session. We choose to
249use the
250{doc}classWt_1_1Dbo_1_1Session.html[Wt::Dbo::Session]
251class as a base class (which could just as well be an embedded
252member).
253
254Later on, we'll see how each web session will instantiate its own
255persistence/authentication +Session+ object.
256
257.Session.h
258[source,cpp]
259----
260#include <Wt/Auth/Login.h>
261#include <Wt/Auth/UserDatabase.h>
262
263#include <Wt/Dbo/Session.h>
264#include <Wt/Dbo/ptr.h>
265
266#include "User.h"
267
268namespace dbo = Wt::Dbo;
269
270using UserDatabase = Wt::Auth::Dbo::UserDatabase<AuthInfo>;
271
272class Session : public dbo::Session
273{
274public:
275  Session(const std::string& sqliteDb);
276
277  Wt::Auth::AbstractUserDatabase& users();
278  Wt::Auth::Login& login() { return login_; }
279
280  ...
281
282private:
283  std::unique_ptr<UserDatabase> users_;
284  Wt::Auth::Login login_;
285
286  ...
287};
288----
289
290Notice the type alias for +UserDatabase+, which states that we will be
291using the
292{doc}classWt_1_1Auth_1_1Dbo_1_1UserDatabase.html[Wt::Auth::Dbo::UserDatabase]
293implementation using +AuthInfo+, for which we declared a type alias
294earlier on. You are of course free to provide another implementation
295for
296{doc}classWt_1_1Auth_1_1AbstractUserDatabase.html[Wt::Auth::AbstractUserDatabase]
297which is not based on +Wt::Dbo+.
298
299We also embed a
300{doc}classWt_1_1Auth_1_1Login.html[Wt::Auth::Login]
301member here, which is a small model class that holds the current login
302information. The login/logout widgets will manipulate this login
303object, while the rest of our application will listen to login changes
304from this object to adapt to the user currently logged in.
305
306The +Session+ constructor sets up the database session.
307
308.Session.C (constructor)
309[source,cpp]
310----
311#include "Session.h"
312#include "User.h"
313
314#include "Wt/Auth/Dbo/AuthInfo.h"
315
316#include "Wt/Dbo/backend/Sqlite3.h"
317
318using namespace Wt;
319
320Session::Session(const std::string& sqliteDb)
321{
322  auto connection = std::make_unique<Dbo::backend::Sqlite3>(sqliteDb);
323  setConnection(std::move(connection_));
324
325  mapClass<User>("user");
326  mapClass<AuthInfo>("auth_info");
327  mapClass<AuthInfo::AuthIdentityType>("auth_identity");
328  mapClass<AuthInfo::AuthTokenType>("auth_token");
329
330  try {
331    createTables();
332    std::cerr << "Created database." << std::endl;
333  } catch (Wt::Dbo::Exception& e) {
334    std::cerr << e.what() << std::endl;
335    std::cerr << "Using existing database";
336  }
337
338  users_ = std::make_unique<UserDatabase>(*this);
339}
340----
341
342The example uses a SQLite3 database (a cuddly database convenient for
343development), and we map four persistence classes to tables.
344
345We then create the data schema if needed, which will automatically
346issue the following SQL:
347
348[source,sql]
349----
350create table "user" (
351  "id" integer primary key autoincrement,
352  "version" integer not null
353)
354
355create table "auth_info" (
356  "id" integer primary key autoincrement,
357  "version" integer not null,
358  "user_id" bigint,
359  "password_hash" varchar(100) not null,
360  "password_method" varchar(20) not null,
361  "password_salt" varchar(20) not null,
362  "status" integer not null,
363  "failed_login_attempts" integer not null,
364  "last_login_attempt" text,
365  "email" varchar(256) not null,
366  "unverified_email" varchar(256) not null,
367  "email_token" varchar(64) not null,
368  "email_token_expires" text,
369  "email_token_role" integer not null,
370  constraint "fk_auth_info_user" foreign key
371   ("user_id") references "user" ("id")
372   on delete cascade deferrable initially deferred
373)
374
375create table "auth_token" (
376  "id" integer primary key autoincrement,
377  "version" integer not null,
378  "auth_info_id" bigint,
379  "value" varchar(64) not null,
380  "expires" text,
381  constraint "fk_auth_token_auth_info" foreign key
382   ("auth_info_id") references "auth_info" ("id")
383   on delete cascade deferrable initially deferred
384)
385
386create table "auth_identity" (
387  "id" integer primary key autoincrement,
388  "version" integer not null,
389  "auth_info_id" bigint,
390  "provider" varchar(64) not null,
391  "identity" varchar(512) not null,
392  constraint "fk_auth_identity_auth_info" foreign key
393   ("auth_info_id") references "auth_info" ("id")
394   on delete cascade defferable initially deferred
395)
396----
397
398Notice the +auth_info+, +auth_token+ and +auth_identity+ tables that
399define the storage for our authentication system.
400
401=== Configuring authentication
402
403The service classes
404({doc}classWt_1_1Auth_1_1AuthService.html[Wt::Auth::AuthService],
405{doc}classWt_1_1Auth_1_1PasswordService.html[Wt::Auth::PasswordService],
406and
407{doc}classWt_1_1Auth_1_1OAuthService.html[Wt::Auth::OAuthService]),
408can be shared between sessions and contain the configuration and logic
409which does not require transient session state.
410
411A good location to add these service classes are inside a specialized
412{doc}classWt_1_1WServer.html[Wt::WServer]
413instance, of which you usually also have only one in a Wt process. You
414could also create a singleton for them. To keep the example simple, we
415will declare them simply as global variables (but within file scope):
416+myAuthService+, +myPasswordService+, and +myOAuthServices+.
417
418.Session.C (authentication services)
419[source,cpp]
420----
421
422#include "Wt/Auth/AuthService.h"
423#include "Wt/Auth/HashFunction.h"
424#include "Wt/Auth/PasswordService.h"
425#include "Wt/Auth/PasswordStrengthValidator.h"
426#include "Wt/Auth/PasswordVerifier.h"
427#include "Wt/Auth/GoogleService.h"
428#include "Wt/Auth/FacebookService.h"
429
430namespace {
431  Wt::Auth::AuthService myAuthService;
432  Wt::Auth::PasswordService myPasswordService{myAuthService};
433  std::vector<std::unique_ptr<Wt::Auth::OAuthService>> myOAuthServices;
434}
435
436void Session::configureAuth()
437{
438  myAuthService.setAuthTokensEnabled(true, "logincookie");
439  myAuthService.setEmailVerificationEnabled(true);
440  myAuthService.setEmailVerificationRequired(true);
441
442  std::unique_ptr<Wt::Auth::PasswordVerifier> verifier =
443    std::make_unique<Wt::Auth::PasswordVerifier>();
444  verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(7));
445  myPasswordService.setVerifier(std::move(verifier));
446  myPasswordService.setAttemptThrottlingEnabled(true);
447  myPasswordService.setStrengthValidator(std::make_unique<Wt::Auth::PasswordStrengthValidator>());
448
449  if (Wt::Auth::GoogleService::configured())
450    myOAuthServices.push_back(std::make_unique<Wt::Auth::GoogleService>(myAuthService));
451
452  if (Wt::Auth::FacebookService::configured())
453    myOAuthServices.push_back(std::make_unique<Wt::Auth::FacebookService>(myAuthService));
454
455  for (unsigned i = 0; i < myOAuthServices.size(); ++i)
456    myOAuthServices[i]->generateRedirectEndpoint();
457}
458
459Wt::Auth::AbstractUserDatabase& Session::users()
460{
461  return *users_;
462}
463
464const Wt::Auth::AuthService& Session::auth()
465{
466  return myAuthService;
467}
468
469const Wt::Auth::PasswordService& Session::passwordAuth()
470{
471  return myPasswordService;
472}
473
474const std::vector<const Wt::Auth::OAuthService *>& Session::oAuth()
475{
476  std::vector<const Wt::Auth::OAuthService *> result;
477  for (auto &auth : myOAuthServices) {
478    result.push_back(auth.get());
479  }
480  return result;
481}
482----
483
484The {doc}classWt_1_1Auth_1_1AuthService.html[Wt::Auth::AuthService]
485is configured to support "remember-me" functionality, and email
486verification.
487
488The
489{doc}classWt_1_1Auth_1_1PasswordService.html[Wt::Auth::PasswordService]
490needs a hash function to safely store passwords. You can actually
491define more than one hash function, which is useful only if you want
492to migrate to a new hash function while still supporting existing
493passwords. When a user logs in, and he is not using the "preferred"
494hash function, his password will be rehashed with the preferred
495one. In this example, we will use
496http://en.wikipedia.org/wiki/Bcrypt[bcrypt] which is included as a
497{doc}classWt_1_1Auth_1_1HashFunction.html[hash
498function] in Wt::Auth.
499
500We also enable password attempt throttling: this mitigates brute force
501password guessing attempts.
502
503Finally, we also use two (but later, perhaps more)
504{doc}classWt_1_1Auth_1_1OAuthService.html[Wt::Auth::OAuthService]
505classes. You need one service per identity provider. In this case,
506we add Google and Facebook as identity providers.
507
508=== The user interface
509
510We create a specialized
511{doc}classWt_1_1WApplication.html[Wt::WApplication]
512which contains our authentication session, and instantiates a
513{doc}classWt_1_1Auth_1_1AuthWidget.html[Wt::Auth::AuthWidget]. This
514widget shows a login or logout form (depending on the login status),
515and also hooks into default forms for registration, lost passwords,
516and handling of email-sent tokens in URLs).
517
518.User interface
519[source,cpp]
520----
521#include <Wt/WApplication.h>
522#include <Wt/WBootstrapTheme.h>
523#include <Wt/WContainerWidget.h>
524#include <Wt/WServer.h>
525
526#include <Wt/Auth/AuthWidget.h>
527#include <Wt/Auth/PasswordService.h>
528
529#include "model/Session.h"
530
531class AuthApplication : public Wt::WApplication
532{
533public:
534  AuthApplication(const Wt::WEnvironment& env)
535    : Wt::WApplication(env),
536      session_(appRoot() + "auth.db")
537  {
538    session_.login().changed().connect(this, &AuthApplication::authEvent);
539
540    root()->addStyleClass("container");
541    setTheme(std::make_shared<Wt::WBootstrapTheme>());
542
543    useStyleSheet("css/style.css");
544
545    std::unique_ptr<Wt::Auth::AuthWidget> authWidget =
546      std::make_unique<Wt::Auth::AuthWidget>(Session::auth(), session_.users(), session_.login());
547
548    authWidget->model()->addPasswordAuth(&Session::passwordAuth());
549    authWidget->model()->addOAuth(Session::oAuth());
550    authWidget->setRegistrationEnabled(true);
551
552    authWidget->processEnvironment();
553
554    root()->addWidget(std::move(authWidget));
555  }
556
557  void authEvent() {
558    if (session_.login().loggedIn()) {
559      const Wt::Auth::User& u = session_.login().user();
560      log("notice")
561        << "User " << u.id()
562        << " (" << u.identity(Wt::Auth::Identity::LoginName) << ")"
563        << " logged in.";
564    } else
565      log("notice") << "User logged out.";
566  }
567
568private:
569  Session session_;
570};
571----
572
573The last part is our main function where we setup the application server:
574
575.Application server setup
576[source,cpp]
577----
578std::unique_ptr<Wt::WApplication> createApplication(const Wt::WEnvironment &env)
579{
580  return std::make_unique<AuthApplication>(env);
581}
582
583int main(int argc, char **argv)
584{
585  try {
586    Wt::WServer server{argc, argv, WTHTTP_CONFIGURATION};
587
588    server.addEntryPoint(Wt::EntryPointType::Application, createApplication);
589
590    Session::configureAuth();
591
592    server.run();
593  } catch (Wt::WServer::Exception& e) {
594    std::cerr << e.what() << std::endl;
595  } catch (Wt::Dbo::Exception &e) {
596    std::cerr << "Dbo exception: " << e.what() << std::endl;
597  } catch (std::exception &e) {
598    std::cerr << "exception: " << e.what() << std::endl;
599  }
600}
601----
602
603
604