diff --git a/conf/create-manager-db.sql b/conf/create-manager-db.sql
index 43158aa5434bbc192f043b6783a8b74cc3d541d7..01112fb10c90ed24d420d3d9700bd6c22b596ea9 100644
--- a/conf/create-manager-db.sql
+++ b/conf/create-manager-db.sql
@@ -30,6 +30,8 @@ CREATE TABLE `services` (
 
 CREATE TABLE `accounts` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `token` varchar(50) NOT NULL,
+  `password_crypt` varchar(50) NOT NULL,
   `password_hash` varchar(50) NOT NULL,
   `creation_date` datetime DEFAULT NULL,
   `expiration_date` datetime DEFAULT NULL,
diff --git a/lib/AccountManager/Account.pm b/lib/AccountManager/Account.pm
index 7417165676397a77058ea4e0a302833ef92f6221..f89f1202864c9c4d887c3f2efa254ef2be376bb6 100644
--- a/lib/AccountManager/Account.pm
+++ b/lib/AccountManager/Account.pm
@@ -12,7 +12,9 @@ __PACKAGE__->meta->setup(
     columns => [
         id              => { type => 'bigserial', not_null => 1 },
         password_hash   => { type => 'varchar', length => 50, not_null => 1 },
+        password_crypt  => { type => 'varchar', length => 50, not_null => 1 },
         password        => { type => 'varchar', length => 50, nonpersistent => 1 },
+        token           => { type => 'varchar', length => 50, not_null => 1 },
         creation_date   => { type => 'datetime' },
         expiration_date => { type => 'datetime' },
         profile         => { type => 'varchar', length => 100, not_null => 1 },
diff --git a/lib/AccountManager/WebRequest.pm b/lib/AccountManager/WebRequest.pm
index e2fa0b918ce6ce4973d0641854073929fa9105f4..ec9ee1016f545d0f2d1af4e1a0b6a2355061314a 100644
--- a/lib/AccountManager/WebRequest.pm
+++ b/lib/AccountManager/WebRequest.pm
@@ -6,13 +6,10 @@ use warnings;
 use CGI;
 use DateTime;
 use English qw(-no_match_vars);
+use HTTP::AcceptLanguage;
 use Template;
 use Log::Any::Adapter;
 use List::MoreUtils qw(uniq);
-use HTTP::AcceptLanguage;
-use HTTP::Message;
-use HTTP::Response;
-use IO::String;
 use Text::CSV;
 
 use AccountManager::Account;
@@ -40,6 +37,7 @@ my %actions = (
     select_email       => 'req_select_email',
     complete_challenge => 'req_complete_challenge',
     create_accounts    => 'req_create_accounts',
+    download_accounts  => 'req_download_accounts',
 );
 
 ## New web request
@@ -109,7 +107,8 @@ sub run {
             email    => $parameters{email},
             style    => $parameters{style},
             entityid => $parameters{entityid},
-            token    => $parameters{token}
+            token    => $parameters{token},
+            key      => $parameters{key},
         };
     }
 
@@ -158,7 +157,6 @@ sub respond {
     binmode(STDOUT, ":utf8");
 
     print $self->{cgi}->header(
-        -nph     => 1,
         -type    => 'text/html',
         -charset => 'utf8'
     );
@@ -413,6 +411,11 @@ sub req_create_accounts {
         $self->respond({ errors => [ "missing_token" ] });
     }
 
+    unless ($self->{in}->{email}) {
+        $self->{logger}->error("Missing parameter email");
+        $self->respond({ errors => [ "missing_email" ] });
+    }
+
     my $token = AccountManager::Token->new(
         db    => $self->{db},
         token => $self->{in}->{token}
@@ -455,6 +458,24 @@ sub req_create_accounts {
         $self->{configuration}->{$entity}->{account_validity_period} ||
         $self->{configuration}->{service}->{account_validity_period};
 
+
+    my $download_token = AccountManager::Token->new(
+        db              => $self->{db},
+        email_address   => $self->{in}->{email},
+        sp_entityid     => $self->{in}->{entityid},
+        creation_date   => DateTime->now(),
+        expiration_date => DateTime->now()->add(hours => $validity_period),
+        token           => AccountManager::Tools::generate_secret(20)
+    );
+
+    unless ($download_token->save()) {
+        push @{ $self->{out}->{errors} }, "internal";
+        $self->{logger}->error("Failed to save authentication token");
+        $self->respond();
+    }
+
+    my $key = AccountManager::Tools::generate_secret(10);
+
     foreach my $profile (split(/, */, $profiles)) {
         my $password = AccountManager::Tools::generate_password(10);
         my $account = AccountManager::Account->new(
@@ -463,7 +484,9 @@ sub req_create_accounts {
             sp_entityid     => $entity,
             scope           => $self->{configuration}->{idp}->{scope},
             password        => $password,
+            password_crypt  => AccountManager::Tools::encrypt($password, $key),
             password_hash   => AccountManager::Tools::sha256_hash($password),
+            token           => $download_token->token(),
             creation_date   => DateTime->now(),
             expiration_date => DateTime->now()->add(days => $validity_period)
         );
@@ -505,55 +528,85 @@ sub req_create_accounts {
         $self->{in}->{token}
     );
 
-    binmode(STDOUT, ":utf8");
+    $self->respond({
+        accounts => \@accounts,
+        entityid => $self->{in}->{entityid},
+        key      => $key,
+        token    => $download_token->token(),
+        action   => 'create_accounts'
+    });
+}
 
-    my $response = HTTP::Response->new(
-        200, 'OK', [ 'Content-Type' => 'multipart/x-mixed-replace' ]
-    );
+sub req_download_accounts {
+    my ($self) = @_;
 
-    $response->protocol('HTTP/1.1');
-    $response->date(time);
-    $response->server($ENV{SERVER_SOFTWARE});
+    unless ($self->{in}->{entityid}) {
+        push @{ $self->{out}->{errors} }, "missing_entityid";
+        $self->{logger}->error("Missing parameter entityid");
+        $self->respond();
+    }
 
-    # HTML page
-    my $data = {
-        app => {
-            name          => $self->{configuration}->{app}->{name},
-            url           => $self->{configuration}->{app}->{url},
-            support_email => $self->{configuration}->{app}->{support_email},
-            version       => $self->{configuration}->{app}->{version},
-        },
-        accounts                 => \@accounts,
-        accounts_validity_period => $self->{configuration}->{service}->{account_validity_period},
-        idp_displayname          => $self->{configuration}->{idp}->{displayname},
-        entityid                 => $self->{in}->{entityid},
-        action                   => 'create_accounts'
-    };
+    unless ($self->{in}->{token}) {
+        push @{ $self->{out}->{errors} }, "missing_token";
+        $self->{logger}->error("Missing parameter token");
+        $self->respond();
+    }
 
-    my $lang = HTTP::AcceptLanguage->new($ENV{HTTP_ACCEPT_LANGUAGE})->match(qw/en fr/) || 'en';
+    unless ($self->{in}->{key}) {
+        push @{ $self->{out}->{errors} }, "missing_key";
+        $self->{logger}->error("Missing parameter key");
+        $self->respond();
+    }
 
-    my $tt2 = Template->new({
-        ENCODING => 'utf8',
-        INCLUDE_PATH => $self->{configuration}->{_}->{templates_dir} . "/web/$lang"
-    });
+    my $token = AccountManager::Token->new(
+        db    => $self->{db},
+        token => $self->{in}->{token}
+    );
+
+    if (! $token->load(speculative => 1)) {
+        push @{ $self->{out}->{errors} }, "wrong_token";
+        $self->{logger}->errorf(
+            "Non-existing authentication token %s",
+            $self->{in}->{token},
+        );
+        $self->respond();
+    }
 
-    my $page_content;
-    $tt2->process('index.tt2.html', $data, \$page_content);
+    if (! $token->sp_entityid() eq $self->{in}->{entityid}) {
+        push @{ $self->{out}->{errors} }, "wrong_token_for_sp";
+        $self->{logger}->errorf(
+            "Authentication token %s cannot be used for SP %s",
+            $self->{in}->{token},
+            $self->{in}->{entityid}
+        );
+        $self->respond();
+    }
 
-    my $page_response = HTTP::Message->new(
-        [
-            'Content-Type'        => 'text/html; charset=utf-8',
-            'Content-Disposition' => 'inline'
+    # delete the token
+    unless ($token->delete()) {
+        $self->{logger}->errorf(
+            "Failed to delete authentication token %s",
+            $self->{in}->{token}
+        );
+    }
+
+    # load accounts from database
+    my $accounts = AccountManager::Account::Manager->get_accounts(
+        db    => $self->{db},
+        query => [
+            token => $self->{in}->{token}
         ],
-        $page_content
     );
-    $response->add_part($page_response);
 
-    # CSV file
-    my $csv = Text::CSV->new({ binary => 1, eol => "\r\n", quote_space => 0 });
-    my $file_content;
-    my $file_content_io = IO::String->new($file_content);
-    $csv->print($file_content_io, [ qw/
+    binmode(STDOUT, ":utf8");
+
+    print $self->{cgi}->header(
+        -type                => 'text/csv',
+        -content_disposition => 'attachment; filename="accounts.csv"'
+    );
+
+    my $csv = Text::CSV->new ({ binary => 1, eol => "\r\n", quote_space => 0 });
+    $csv->print(\*STDOUT, [ qw/
         username
         password
         profile
@@ -568,8 +621,13 @@ sub req_create_accounts {
         schacHomeOrganizationType
     / ]);
 
-    foreach my $account (@accounts) {
-        $csv->print($file_content_io, [
+    foreach my $account (@$accounts) {
+        my $password = AccountManager::Tools::decrypt(
+            $account->password_crypt(),
+            $self->{in}->{key}
+        );
+        $account->password($password);
+        $csv->print(\*STDOUT, [
             $account->internal_uid(),
             $account->password(),
             $account->profile(),
@@ -584,17 +642,6 @@ sub req_create_accounts {
             $account->schacHomeOrganizationType(),
         ]);
     }
-
-    my $file_response = HTTP::Message->new(
-        [
-            'Content-Type'        => 'text/csv; charset=utf-8',
-            'Content-Disposition' => 'attachment; filename="accounts.csv"'
-        ],
-        $file_content
-    );
-    $response->add_part($file_response);
-
-    print $response->as_string();
 }
 
 ## Return the homepage of the service
diff --git a/templates/web/en/create_accounts.tt2.html b/templates/web/en/create_accounts.tt2.html
index a93abf4531de9dd471cfae89d91c4432b3019e1c..1c2c9a7b1a97f5fd2f489621ca93ba67eeec82f6 100644
--- a/templates/web/en/create_accounts.tt2.html
+++ b/templates/web/en/create_accounts.tt2.html
@@ -7,6 +7,8 @@
 do so, select <strong>[% idp_displayname %]</strong> when choosing an identity
 provider.</p>
 
+<p>Click <a href="[% conf.app_url %]?action=download_accounts&entityid=[% entityid %]&token=[% token %]&key=[% key %]">here</a> to download the list of those accounts in CSV format.</p>
+
 <div class="accounts_profile">
     [% FOREACH account IN accounts %]
     <div class="tbl">
diff --git a/templates/web/en/select_sp.tt2.html b/templates/web/en/select_sp.tt2.html
index fd5cedc6845964327c9496d20fefc881b0343737..f4a4f90ea4ca1612b86c52717a9926532d974cb6 100644
--- a/templates/web/en/select_sp.tt2.html
+++ b/templates/web/en/select_sp.tt2.html
@@ -44,7 +44,8 @@ jQuery(document).ready(function($){
             if (currentIndex === 2 && newIndex === 3)
             {
                 window.location="[% app.url %]?action=create_accounts&entityid="+
-                        encodeURIComponent($('#entityid').val())+"&token="+encodeURIComponent($('#token').val());
+                    encodeURIComponent($('#entityid').val())+"&token="+encodeURIComponent($('#token').val()) +
+                    "&email="+encodeURIComponent($('#email').val());
              }
 
             // Allways allow previous action even if the current form is not valid!
diff --git a/templates/web/fr/create_accounts.tt2.html b/templates/web/fr/create_accounts.tt2.html
index 37d2ca04d5a125e8530c03ffe4469f3d62d34a05..8d87f173a3af398baf5fe139525043a19ea02447 100644
--- a/templates/web/fr/create_accounts.tt2.html
+++ b/templates/web/fr/create_accounts.tt2.html
@@ -7,6 +7,8 @@
 service fédéré. Pour le faire, sélectionnez <strong>[% idp_displayname
 %]</strong> lors du choix du founisseur d'identité à utiliser.</p>
 
+<p>Cliquez <a href="[% conf.app_url %]?action=download_accounts&entityid=[% entityid %]&token=[% token %]&key=[% key %]">ici</a> pour télécharger la liste de ces comptes au format CSV.</p>
+
 <div class="accounts_profile">
     [% FOREACH account IN accounts %]
     <div class="tbl">
diff --git a/templates/web/fr/select_sp.tt2.html b/templates/web/fr/select_sp.tt2.html
index a1ae369f40aa5f5139a1774b017b0ac91e1300f5..afb0b3ff4cd324421ea837712434a764722e9c7a 100644
--- a/templates/web/fr/select_sp.tt2.html
+++ b/templates/web/fr/select_sp.tt2.html
@@ -44,7 +44,8 @@ jQuery(document).ready(function($){
             if (currentIndex === 2 && newIndex === 3)
             {
                 window.location="[% app.url %]?action=create_accounts&entityid="+
-                        encodeURIComponent($('#entityid').val())+"&token="+encodeURIComponent($('#token').val());
+                    encodeURIComponent($('#entityid').val())+"&token="+encodeURIComponent($('#token').val()) +
+                    "&email="+encodeURIComponent($('#email').val());
              }
 
             // Allways allow previous action even if the current form is not valid!