Improve UX of the second factor page

Start the U2F signing request when entering in the second factor page so that
the user only has to touch the token without any other clicks.
This commit is contained in:
Clement Michaud 2017-10-22 01:23:26 +02:00
parent 1e05c41a0c
commit 3052c883a0
27 changed files with 179 additions and 172 deletions

View File

@ -1,61 +1,66 @@
body {
background-image: url("/img/background.svg");
}
.authelia-brand {
font-weight: bold;
font-style: italic;
color: #648caf
}
.poweredby-block {
margin: 0px 30px;
margin-top: 10px;
padding-top: 15px;
border-top: 1px solid rgba(0, 0, 0, 0.15);
}
.poweredby {
font-size: 0.7em;
color: #6b6b6b;
}
/* notifications */
.notification {
padding: 10px;
margin: 10px 0px;
border-radius: 6px;
display: none;
}
.notification img {
width: 24px;
margin-right: 10px;
}
.notification i,
.notification span {
display:table-cell;
vertical-align:middle;
}
.info {
border: 1px solid #9cb1ff;
background-color: rgb(192, 220, 255);
}
.success {
border: 1px solid #65ec7c;
background-color: rgb(163, 255, 157);
}
.error {
border: 1px solid #ffa3a3;
background-color: rgb(255, 175, 175);
}
.warning {
border: 1px solid #ffd743;
background-color: rgb(255, 230, 143);
}
.bottom-right-links {
text-align: right;
margin-top: 10px;
font-size: 0.8em;
}
.header {
background-color: #778dab;
color: white;
margin: 0px;
}
.body {
padding: 10px;
}
h1 {
font-size: 30px;
}

View File

@ -1,6 +1,5 @@
.form-signin
{
padding: 15px;
margin: 0 auto;
}
@ -44,8 +43,7 @@
{
border: 1px solid #DDD;
margin-top: 20px;
padding: 20px;
padding-bottom: 15px;
padding-bottom: 20px;
background-color: #f7f7f7;
-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
@ -53,18 +51,20 @@
}
.account-wall h1
{
color: #555;
margin-bottom: 30px;
font-weight: 400;
margin-bottom: 20px;
font-weight: 800;
display: block;
text-align: center;
}
.account-wall h3
{
display: block;
text-align: center;
}
.account-wall p
{
text-align: center;
margin: 10px 10px;
margin-top: 20px;
font-size: 1.3em;
margin: 10px;
}
.account-wall .form-inputs
{
@ -99,3 +99,11 @@
{
background-color: rgb(83, 149, 204);
}
.u2f-token {
text-align: center;
}
.u2f-token img {
width: 70px;
}

View File

@ -6,20 +6,16 @@
border: 1px solid #c7c7c7;
word-wrap: break-word;
}
.totp-register #qrcode img {
margin: 20px auto;
margin: 10px auto;
}
.totp-register .need-google-authenticator {
text-align: center;
margin: 20px;
margin-top: 20px;
}
.totp-register .store-badges {
margin-top: 5px;
}
.totp-register .store-badge {
width: 110px;
height: 30px;

View File

@ -1,5 +1,3 @@
export const TOTP_FORM_SELECTOR = ".form-signin.totp";
export const TOTP_TOKEN_SELECTOR = ".form-signin #token";
export const U2F_FORM_SELECTOR = ".form-signin.u2f";

View File

@ -49,14 +49,9 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
return false;
}
function onU2FFormSubmitted(): boolean {
U2FValidator.validate($, notifierU2f, U2fApi)
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
return false;
}
$(window.document).ready(function () {
$(ClientConstants.TOTP_FORM_SELECTOR).on("submit", onTOTPFormSubmitted);
$(ClientConstants.U2F_FORM_SELECTOR).on("submit", onU2FFormSubmitted);
U2FValidator.validate($, notifierU2f, U2fApi)
.then(onU2fAuthenticationSuccess, onU2fAuthenticationFailure);
});
}

View File

@ -41,7 +41,7 @@ export default class RegistrationHandler implements IdentityValidable {
const email = authSession.email;
if (!(userid && email)) {
return BluebirdPromise.reject(new Error("User ID or email is missing"));
return BluebirdPromise.reject(new Error("User ID or email is missing."));
}
const identity = {
@ -79,7 +79,7 @@ export default class RegistrationHandler implements IdentityValidable {
return BluebirdPromise.reject(new Error("Bad challenge."));
}
const secret = that.totp.generate();
that.logger.debug(req, "Save the TOTP secret in DB");
that.logger.debug(req, "Save the TOTP secret in DB.");
return that.userDataStore.saveTOTPSecret(userid, secret)
.then(function () {
AuthenticationSession.reset(req);
@ -95,6 +95,6 @@ export default class RegistrationHandler implements IdentityValidable {
}
mailSubject(): string {
return "Register your TOTP secret key";
return "Set up Authelia's one-time password";
}
}

View File

@ -11,7 +11,7 @@ import AuthenticationSession = require("../../../../AuthenticationSession");
import { IRequestLogger } from "../../../../logging/IRequestLogger";
const CHALLENGE = "u2f-register";
const MAIL_SUBJECT = "Register your U2F device";
const MAIL_SUBJECT = "Register your security key with Authelia";
const POST_VALIDATION_TEMPLATE_NAME = "u2f-register";

View File

@ -1,10 +1,10 @@
extends layout/layout.pug
block form-header
<h1>Sign in</h1>
<img class="header-img" src="/img/success.png" alt="">
h1 Sign in
block content
<p>You are already logged in as <b>#{ username }</b>.<br/><br/>
img(class="header-img" src="/img/success.png" alt="success")
p You are already logged in as <b>#{ username }</b>.<br/><br/>
| If you are not redirected in few seconds, click <a href="#{ redirection_url }">here</a>.<br/><br/>
| Otherwise, click <a href="#{ logout_endpoint }">here</a> to log off.<p>
| Otherwise, click <a href="#{ logout_endpoint }">here</a> to log off.

View File

@ -4,8 +4,8 @@ block variables
- page_classname = "error-401";
block form-header
<h1>Error 401</h1>
<img class="header-img" src="/img/warning.png" alt="">
h1 Error 401
block content
<p>Please <a href="/">log in</a> to access this resource.</p>
img(class="header-img" src="/img/warning.png" alt="warning")
p Please <a href="/">log in</a> to access this resource.

View File

@ -4,8 +4,8 @@ block variables
- page_classname = "error-403";
block form-header
<h1>Error 403</h1>
<img class="header-img" src="/img/warning.png" alt="">
h1 Error 403
block content
<p>You are not authorized to access this resource.</p>
img(class="header-img" src="/img/warning.png" alt="warning")
p You are not authorized to access this resource.

View File

@ -5,7 +5,7 @@ block variables
block form-header
<h1>Error 404</h1>
<img class="header-img" src="/img/warning.png" alt="">
block content
<p>Page not found.</p>
img(class="header-img" src="/img/warning.png" alt="warning")
p Page not found.

View File

@ -1,17 +1,20 @@
extends layout/layout.pug
block variables
- page_classname = "firstfactor";
block form-header
<h1>Sign in</h1>
<img class="header-img" src="/img/user.png" alt="">
h1 Sign in
block content
<div class="notification"></div>
<form class="form-signin">
<div class="form-inputs">
<input type="text" class="form-control" id="username" placeholder="Username" required autofocus>
<input type="password" class="form-control" id="password" placeholder="Password" required>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
a(href=reset_password_request_endpoint, class="pull-right link forgot-password") Forgot password?
<span class="clearfix"></span>
</form>
img(class="header-img" src="/img/user.png" alt="user profile")
p Enter your credentials to sign in
div(class="notification")
form(class="form-signin")
div(class="form-inputs")
input(type="text" class="form-control" id="username" placeholder="Username" required autofocus)
input(type="password" class="form-control" id="password" placeholder="Password" required)
button(class="btn btn-lg btn-primary btn-block" type="submit") Sign in
div(class="bottom-right-links pull-right")
a(href=reset_password_request_endpoint, class="link forgot-password") Forgot password?
span(class="clearfix")

View File

@ -9,24 +9,19 @@ html
link(rel="icon", href="/img/icon.png" type="image/png" sizes="32x32")/
link(rel="stylesheet", type="text/css", href="/css/authelia.css")/
if redirection_url
<meta http-equiv="refresh" content="4;url=#{redirection_url}">
meta(http-equiv="refresh" content="4;url=" + redirection_url)
body
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3">
<div class="account-wall #{page_classname}">
div(class="container")
div(class="row")
div(class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3")
div(class="account-wall " + page_classname)
div(class="row header")
block form-header
<div class="row">
<div class="form col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-8 col-md-offset-2">
div(class="row body")
div(class="form col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2")
block content
</div>
</div>
<div class="row poweredby-block">
<div class="poweredby col-xs-6 col-xs-offset-4 col-sm-6 col-sm-offset-4 col-md-6 col-md-offset-4">Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a></div>
</div>
</div>
</div>
</div>
</div>
div(class="row footer poweredby-block")
div(class="poweredby col-xs-6 col-xs-offset-4 col-sm-6 col-sm-offset-4 col-md-6 col-md-offset-4")
| Powered by <a class="authelia-brand" href="https://github.com/clems4ever/authelia">Authelia</a>
block entrypoint
script(src="/js/authelia.js")

View File

@ -1,8 +1,12 @@
extends layout/layout.pug
block variables
- page_classname = "identity-validation";
block form-header
<h1>Registration</h1>
<img class="header-img" src="/img/mail.png" alt="">
h1 Registration
block content
<p>A confirmation email has been sent to your mailbox. Please open it and click on the link within 15 minutes to confirm the registration.</p>
img(class="header-img" src="/img/mail.png" alt="mail")
p A confirmation email has been sent to your mailbox.
| Please open it and click on the link within 15 minutes to confirm the registration.

View File

@ -4,17 +4,15 @@ block variables
- page_classname = "password-reset-form";
block form-header
<h1>Reset password</h1>
<img class="header-img" src="/img/password.png" alt="">
<p>Set your new password and confirm it.</p>
h1 Reset password
block content
<div class="notification"></div>
<form class="form-signin">
<div class="form-inputs">
<input class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required" />
<input class="form-control" type="password" name="password2" id="password2" placeholder="Password confirmation" required="required" />
</div>
<button id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit">Reset Password</button>
<span class="clearfix"></span>
</form>
img(class="header-img" src="/img/password.png" alt="password")
p Set your new password and confirm it.
div(class="notification")
form(class="form-signin")
div(class="form-inputs")
input(class="form-control" type="password" name="password1" id="password1" placeholder="New password" required="required")
input(class="form-control" type="password" name="password2" id="password2" placeholder="Password confirmation" required="required")
button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
span(class="clearfix")

View File

@ -4,16 +4,15 @@ block variables
- page_classname = "password-reset-request";
block form-header
<h1>Reset password</h1>
<img class="header-img" src="/img/password.png" alt="">
<p>After giving your username, you will receive an email to change your password.</p>
h1 Reset password
block content
<div class="notification"></div>
<form class="form-signin">
<div class="form-inputs">
<input type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required" />
</div>
<button id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit">Reset Password</button>
<span class="clearfix"></span>
</form>
div
img(class="header-img" src="/img/password.png" alt="password")
p After giving your username, you will receive an email to change your password.
div(class="notification")
form(class="form-signin")
div(class="form-inputs")
input(type="text" class="form-control" name="username" id="username" placeholder="Your username" required="required")
button(id="reset-password-button" class="btn btn-lg btn-primary btn-block" type="submit") Reset Password
span(class="clearfix")

View File

@ -1,24 +1,30 @@
extends layout/layout.pug
block variables
- page_classname = "secondfactor";
block form-header
<h1>Sign in</h1>
<img class="header-img" src="../img/padlock.png" alt="">
h1 Sign in
block content
p Hi <b>#{username}</b>, please complete second factor or <a href="/logout">logout</a>.
<div class="notification notification-totp"></div>
<form class="form-signin totp">
<div class="form-inputs">
<input type="text" autocomplete="off" class="form-control" id="token" placeholder="Token" required autofocus>
</div>
<button class="btn btn-lg btn-primary btn-block totp-button" type="submit">TOTP</button>
a(href=totp_identity_start_endpoint, class="pull-right link register-totp") Need to register?
<span class="clearfix"></span>
</form>
<hr>
<div class="notification notification-u2f"></div>
<form class="form-signin u2f">
<button class="btn btn-lg btn-primary btn-block u2f-button" type="submit">U2F</button>
a(href=u2f_identity_start_endpoint, class="pull-right link register-u2f") Need to register?
<span class="clearfix"></span>
</form>
div
h3 Hi <b>#{username}</b>
div(class="row")
div(class="u2f-token")
img(src="/img/pendrive.png", alt="security key")
p
| touch your U2F device<br/>
b Or<br/>
| get a one-time password
div(class="notification notification-totp")
form(class="form-signin totp")
div(class="form-inputs")
input(type="text" autocomplete="off" class="form-control" id="token" placeholder="Token" required autofocus)
button(class="btn btn-lg btn-primary btn-block totp-button" type="submit") Sign in
div(class="pull-right bottom-right-links")
div Need to register?
div
a(href=u2f_identity_start_endpoint, class="link register-u2f") U2F
| |
a(href=totp_identity_start_endpoint, class="link register-totp") Google Authenticator
span(class="clearfix")

View File

@ -4,12 +4,12 @@ block variables
- page_classname = "totp-register";
block form-header
<h1>One-time passwords</h1>
<p>Open Google Authenticator and add this entry</p>
h1 One-time passwords
block content
p Open Google Authenticator and add this entry
p(id="secret") #{ base32_secret }
p Or scan this barcode
p or scan this barcode
div(id="qrcode") #{ otpauth_url }
p
a(href=login_endpoint, id="login-button") Login
@ -22,4 +22,4 @@ block content
img(alt='Get it on Apple Store' src='/img/stores/applestore-badge.svg' class="store-badge")
block entrypoint
<script src="/js/qrcode.min.js"></script>
script(src="/js/qrcode.min.js")

View File

@ -4,8 +4,8 @@ block variables
- page_classname = "u2f-register";
block form-header
<h1>U2F Registration</h1>
<p>Touch the token to register your U2F device.</p>
h1 Register your security key
block content
<img src="/img/pendrive.png" alt="pendrive" />
p Touch the token to register your security key.
img(src="/img/pendrive.png" alt="pendrive")

View File

@ -5,7 +5,7 @@ Feature: User has access restricted access to domains
When I visit "https://auth.test.local:8080?redirect=https%3A%2F%2Fhome.test.local%3A8080%2F"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
And I'm redirected to "https://home.test.local:8080/"
Then I have access to:
| url |
@ -27,7 +27,7 @@ Feature: User has access restricted access to domains
When I visit "https://auth.test.local:8080?redirect=https%3A%2F%2Fhome.test.local%3A8080%2F"
And I login with user "bob" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
And I'm redirected to "https://home.test.local:8080/"
Then I have access to:
| url |
@ -49,7 +49,7 @@ Feature: User has access restricted access to domains
When I visit "https://auth.test.local:8080?redirect=https%3A%2F%2Fhome.test.local%3A8080%2F"
And I login with user "harry" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
And I'm redirected to "https://home.test.local:8080/"
Then I have access to:
| url |

View File

@ -15,7 +15,7 @@ Feature: User is redirected when factors are already validated
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
And I'm redirected to "https://public.test.local:8080/secret.html"
And I visit "https://auth.test.local:8080"
Then I'm redirected to "https://auth.test.local:8080/loggedin"
@ -28,7 +28,7 @@ Feature: User is redirected when factors are already validated
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
And I'm redirected to "https://public.test.local:8080/secret.html"
And I visit "https://auth.test.local:8080?redirect=https://public.test.local:8080/secret.html"
Then I'm redirected to "https://public.test.local:8080/secret.html"

View File

@ -22,7 +22,7 @@ Feature: Authentication scenarii
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://admin.test.local:8080/secret.html"
Scenario: User fails TOTP second factor
@ -30,7 +30,7 @@ Feature: Authentication scenarii
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "BADTOKEN" as TOTP token
And I click on "TOTP"
And I click on "Sign in"
Then I get a notification of type "error" with message "Authentication failed. Have you already registered your secret?"
Scenario: Logout redirects user to redirect URL given in parameter

View File

@ -12,7 +12,7 @@ Feature: User is correctly redirected
And I clear field "username"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://public.test.local:8080/secret.html"
Scenario: User Harry does not have access to admin domain and thus he must get an error 403
@ -39,7 +39,7 @@ Feature: User is correctly redirected
When I visit "https://public.test.local:8080/secret.html"
And I login with user "john" and password "password"
And I use "Sec0" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://public.test.local:8080/secret.html"
@need-registered-user-john
@ -47,5 +47,5 @@ Feature: User is correctly redirected
When I visit "https://auth.test.local:8080"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://home.test.local:8080/"

View File

@ -35,5 +35,5 @@ Feature: Authelia regulates authentication to avoid brute force
And I set field "password" to "password"
And I click on "Sign in"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://public.test.local:8080/secret.html"

View File

@ -13,5 +13,5 @@ Feature: Authelia keeps user sessions despite the application restart
And I visit "https://admin.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fadmin.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I click on "Sign in"
Then I'm redirected to "https://admin.test.local:8080/secret.html"

View File

@ -97,7 +97,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
return context.useTotpTokenHandle("REGISTERED");
})
.then(function () {
return context.clickOnButton("TOTP");
return context.clickOnButton("Sign in");
});
}

View File

@ -164,7 +164,7 @@ function CustomWorld() {
return that.useTotpTokenHandle(totpHandle);
})
.then(function () {
return that.clickOnButton("TOTP");
return that.clickOnButton("Sign in");
});
};
}