diff --git a/lang/fr.json b/lang/fr.json
index 23fc764fe6c8e6a5a8e66961c9678419691498dd..1a14a820bddc9ce8e3d311d1f0b3f8fcf8e66ec4 100755
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -483,6 +483,12 @@
 		"otp_visa_yousignUser": "viseur (yousign)",
 		"otp_sign_yousignUser": "signataire (yousign)",
 		"role": "Role",
-		"otpMsg": "L'utilisateur sera notifié par <b>mail</b> et recevra un <b>code de sécurité</b> par <b>{{security}}</b> au moment de son tour dans le circuit."
+		"otpMsg": "L'utilisateur sera notifié par <b>mail</b> et recevra un <b>code de sécurité</b> par <b>{{security}}</b> au moment de son tour dans le circuit.",
+		"manage_otp_connectorsAdmin": "Administrer les connecteurs OTP",
+		"manage_otp_connectors": "Connecteurs OTP",
+		"newConnector": "Nouveau connecteur",
+		"connectors": "connecteur(s)",
+		"type": "Type",
+		"otpConnectorCreation": "Création d'un connecteur OTP"
 	}
 }
diff --git a/src/app/group/controllers/PrivilegeController.php b/src/app/group/controllers/PrivilegeController.php
index 5376a42d6663cf4990a7e4b0626bca0973a2d50a..86bc51835fa8c774934ae79f8bcdb70e71fb6bb2 100755
--- a/src/app/group/controllers/PrivilegeController.php
+++ b/src/app/group/controllers/PrivilegeController.php
@@ -27,6 +27,7 @@ class PrivilegeController
         ['id' => 'manage_email_configuration',  'type' => 'admin', 'icon' => 'paper-plane',   'route' => '/administration/emailConfiguration'],
         ['id' => 'manage_password_rules',       'type' => 'admin', 'icon' => 'lock-closed',   'route' => '/administration/passwordRules'],
         ['id' => 'manage_history',              'type' => 'admin', 'icon' => 'timer-outline', 'route' => '/administration/history'],
+        ['id' => 'manage_otp_connectors',       'type' => 'admin', 'icon' => 'people-circle-outline', 'route' => '/administration/otps'],
         ['id' => 'manage_documents',            'type' => 'simple'],
         ['id' => 'indexation',                  'type' => 'simple']
     ];
diff --git a/src/frontend/app/administration/otp/otp-list.component.html b/src/frontend/app/administration/otp/otp-list.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..fedef991a8be048993118dd6a297346143c318bd
--- /dev/null
+++ b/src/frontend/app/administration/otp/otp-list.component.html
@@ -0,0 +1,54 @@
+<ion-header [translucent]="true">
+    <ion-toolbar color="primary">
+        <ion-buttons slot="start">
+            <ion-menu-button menu="left-menu"></ion-menu-button>
+            <ion-back-button></ion-back-button>
+        </ion-buttons>
+        <ion-title>{{'lang.administration' | translate}} {{'lang.manage_otp_connectors' | translate}}</ion-title>
+    </ion-toolbar>
+    <ion-toolbar color="primary">
+        <ion-buttons slot="start">
+            <ion-button fill="outline" shape="round" routerLink="/administration/otps/new">
+                {{'lang.newConnector' | translate}}
+            </ion-button>
+        </ion-buttons>
+        <ion-title slot="end" color="secondary">{{otpList.length}} {{'lang.connectors' | translate}}</ion-title>
+    </ion-toolbar>
+</ion-header>
+<ion-content #mainContent>
+    <ion-list>
+        <ion-item style="display: flex;">
+            <ion-label color="primary" matSort [matSortActive]="displayedColumns[0]" matSortDirection='asc'
+                style="display: flex;font-size: 12px;align-items: center;" (matSortChange)="sortData($event)">
+                <ng-container *ngFor="let col of displayedColumns">
+                    <div [mat-sort-header]="col" disableClear style="flex: 1" *ngIf="col!=='actions'">
+                        {{'lang.' + col | translate}}
+                    </div>
+                </ng-container>
+                <div style="flex: 1" *ngIf="displayedColumns.indexOf('actions') > -1">
+                    <ion-searchbar [placeholder]="'lang.filter' | translate" style="padding: 1px;"
+                        (ionChange)="applyFilter($event.detail.value)"></ion-searchbar>
+                </div>
+            </ion-label>
+            <ion-button slot="end" fill="clear" shape="round" disabled>
+                <ion-icon></ion-icon>
+            </ion-button>
+        </ion-item>
+        <ion-virtual-scroll [items]="sortedData" approxItemHeight="50px">
+            <ion-item *virtualItem="let element" style="display: flex;">
+                <ion-label style="display: flex;cursor: pointer;" routerLink="/administration/otps/{{element.id}}">
+                    <div style="flex: 1" *ngFor="let col of displayedColumns">
+                        <img *ngIf="col === 'type'" [src]="element['logo']" [title]="element[col]" style="width:24px;"/>
+                        <ng-container *ngIf="col !== 'type'">
+                            {{element[col]}}
+                        </ng-container>
+                    </div>
+                </ion-label>
+                <ion-button slot="end" fill="clear" shape="round" (click)="$event.stopPropagation();delete(element)"
+                    title="{{'lang.delete' | translate}}">
+                    <ion-icon color="danger" slot="icon-only" name="trash"></ion-icon>
+                </ion-button>
+            </ion-item>
+        </ion-virtual-scroll>
+    </ion-list>
+</ion-content>
\ No newline at end of file
diff --git a/src/frontend/app/administration/otp/otp-list.component.scss b/src/frontend/app/administration/otp/otp-list.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/frontend/app/administration/otp/otp-list.component.ts b/src/frontend/app/administration/otp/otp-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ec4b0fb02be612101248e2d3d0f2907b12f187f1
--- /dev/null
+++ b/src/frontend/app/administration/otp/otp-list.component.ts
@@ -0,0 +1,159 @@
+import { Component, ViewChild } from '@angular/core';
+import { SignaturesContentService } from '../../service/signatures.service';
+import { NotificationService } from '../../service/notification.service';
+import { HttpClient } from '@angular/common/http';
+import { MatDialog } from '@angular/material/dialog';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort, Sort } from '@angular/material/sort';
+import { MatTableDataSource } from '@angular/material/table';
+import { TranslateService } from '@ngx-translate/core';
+import { map, finalize } from 'rxjs/operators';
+import { LatinisePipe } from 'ngx-pipes';
+import { AlertController } from '@ionic/angular';
+import { OtpService } from '../../document/visa-workflow/otps/otp.service';
+
+
+export interface OtpConnector {
+    id: number;
+    type: string;
+    label: string;
+    logo: string;
+}
+
+@Component({
+    selector: 'app-administration-otp-list',
+    templateUrl: 'otp-list.component.html',
+    styleUrls: ['../administration.scss', 'otp-list.component.scss'],
+})
+
+export class OtpListComponent {
+
+    @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+    @ViewChild(MatSort, { static: true }) sort: MatSort;
+
+    otpList: OtpConnector[] = [];
+    sortedData: any[];
+    dataSource: MatTableDataSource<OtpConnector>;
+    displayedColumns: string[];
+    loading: boolean = true;
+
+    constructor(
+        public http: HttpClient,
+        private translate: TranslateService,
+        private latinisePipe: LatinisePipe,
+        public dialog: MatDialog,
+        public signaturesService: SignaturesContentService,
+        public notificationService: NotificationService,
+        public alertController: AlertController,
+        private otpService: OtpService,
+    ) {
+        this.displayedColumns = ['type', 'label', 'actions'];
+    }
+
+    applyFilter(filterValue: string) {
+        filterValue = this.latinisePipe.transform(filterValue.toLowerCase());
+
+        this.sortedData = this.otpList.filter(
+            (option: any) => {
+                let state = false;
+                this.displayedColumns.forEach(element => {
+                    if (option[element] && this.latinisePipe.transform(option[element].toLowerCase()).includes(filterValue)) {
+                        state = true;
+                    }
+                });
+                return state;
+            }
+        );
+    }
+
+    async ionViewWillEnter() {
+        // FOR TEST
+        this.otpList = [
+            {
+                id: 1,
+                logo : null,
+                type: 'yousign',
+                label: 'YouSign'
+            },
+            {
+                id: 2,
+                logo : null,
+                type: 'yousign',
+                label: 'YouSign 2'
+            }
+        ];
+        for (let index = 0; index < this.otpList.length; index++) {
+            this.otpList[index].logo = await this.otpService.getUserOtpIcon('yousign');
+        }
+        this.sortedData = this.otpList.slice();
+        /* this.http.get('../rest/otps')
+            .pipe(
+                map((data: any) => data.otps),
+                finalize(() => this.loading = false)
+            )
+            .subscribe({
+                next: data => {
+                    this.otpList = data;
+                    for (let index = 0; index < this.otpList.length; index++) {
+                        this.otpList[index].logo = await this.otpService.getUserOtpIcon('yousign');
+                    }
+                    this.sortedData = this.otpList.slice();
+                },
+            });*/
+    }
+
+    async delete(connectorToDelete: OtpConnector) {
+        const alert = await this.alertController.create({
+            // cssClass: 'custom-alert-danger',
+            header: this.translate.instant('lang.confirmMsg'),
+            buttons: [
+                {
+                    text: this.translate.instant('lang.no'),
+                    role: 'cancel',
+                    cssClass: 'secondary',
+                    handler: () => { }
+                },
+                {
+                    text: this.translate.instant('lang.yes'),
+                    handler: () => {
+                        this.http.delete('../rest/otps/' + connectorToDelete.id)
+                            .pipe(
+                                finalize(() => this.loading = false)
+                            )
+                            .subscribe({
+                                next: data => {
+                                    const indexToDelete = this.otpList.findIndex(group => group.id === connectorToDelete.id);
+
+                                    this.otpList.splice(indexToDelete, 1);
+
+                                    this.sortedData = this.otpList.slice();
+
+                                    this.notificationService.success('lang.connectorDeleted');
+
+                                },
+                            });
+                    }
+                }
+            ]
+        });
+
+        await alert.present();
+    }
+
+    sortData(sort: Sort) {
+        const data = this.otpList.slice();
+        if (!sort.active || sort.direction === '') {
+            this.sortedData = data;
+            return;
+        }
+
+        this.sortedData = data.sort((a, b) => {
+            const isAsc = sort.direction === 'asc';
+            return compare(a[sort.active], b[sort.active], isAsc);
+        });
+    }
+}
+
+function compare(a: number | string, b: number | string, isAsc: boolean) {
+    return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/src/frontend/app/administration/otp/otp.component.html b/src/frontend/app/administration/otp/otp.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..f2d47e1fa5e65eeb983ca43d2f00f26f77c75988
--- /dev/null
+++ b/src/frontend/app/administration/otp/otp.component.html
@@ -0,0 +1,25 @@
+<ion-header [translucent]="true">
+    <ion-toolbar color="primary">
+        <ion-buttons slot="start">
+            <ion-menu-button menu="left-menu"></ion-menu-button>
+            <ion-back-button></ion-back-button>
+        </ion-buttons>
+        <ion-title>{{title}}</ion-title>
+    </ion-toolbar>
+</ion-header>
+<form style="display: contents;" id="adminForm" (ngSubmit)="onSubmit()" #adminForm="ngForm">
+    <ion-content>
+        <ion-item>
+            <ion-label color="secondary">{{'lang.source' | translate}}</ion-label>
+            <ion-select name="type" [(ngModel)]="connector.type" [value]="connector.type" cancelText="{{'lang.cancel' | translate}}">
+                <ion-select-option *ngFor="let connector of otpService.getConnectorTypes()" [value]="connector">
+                    {{connector | translate}}</ion-select-option>
+            </ion-select>
+        </ion-item>
+        <ion-item>
+            <ion-label color="secondary" position="floating">{{'lang.label' | translate}} *</ion-label>
+            <ion-input name="label" [maxlength]="128" [(ngModel)]="connector.label" required>
+            </ion-input>
+        </ion-item>
+    </ion-content>
+</form>
\ No newline at end of file
diff --git a/src/frontend/app/administration/otp/otp.component.scss b/src/frontend/app/administration/otp/otp.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/frontend/app/administration/otp/otp.component.ts b/src/frontend/app/administration/otp/otp.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..604a431379b97eb1252e5b161d9041aec7463aac
--- /dev/null
+++ b/src/frontend/app/administration/otp/otp.component.ts
@@ -0,0 +1,178 @@
+import { Component, OnInit } from '@angular/core';
+import { SignaturesContentService } from '../../service/signatures.service';
+import { NotificationService } from '../../service/notification.service';
+import { HttpClient } from '@angular/common/http';
+import { MatDialog } from '@angular/material/dialog';
+import { map, finalize, tap, catchError } from 'rxjs/operators';
+import { ActivatedRoute, Router } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
+import { AuthService } from '../../service/auth.service';
+import { AlertController, ModalController, PopoverController } from '@ionic/angular';
+import { of } from 'rxjs';
+import { OtpService } from '../../document/visa-workflow/otps/otp.service';
+
+export interface Connector {
+    id: string;
+    type: string;
+    label: string;
+    apiUri: string;
+    apiKey: string;
+}
+
+@Component({
+    selector: 'app-administration-otp',
+    templateUrl: 'otp.component.html',
+    styleUrls: ['../administration.scss', 'otp.component.scss'],
+})
+
+export class OtpComponent implements OnInit {
+
+    creationMode: boolean = true;
+    loading: boolean = true;
+    connector: Connector;
+    connectorClone: Connector;
+    title: string = '';
+
+
+    constructor(
+        public http: HttpClient,
+        private translate: TranslateService,
+        private route: ActivatedRoute,
+        private router: Router,
+        public signaturesService: SignaturesContentService,
+        public notificationService: NotificationService,
+        public dialog: MatDialog,
+        public authService: AuthService,
+        public popoverController: PopoverController,
+        public modalController: ModalController,
+        public alertController: AlertController,
+        public otpService: OtpService,
+    ) {
+        this.connector = {
+            id: '',
+            type: this.otpService.getConnectorTypes()[0],
+            label: '',
+            apiUri: '',
+            apiKey: ''
+        };
+        this.connectorClone = JSON.parse(JSON.stringify(this.connector));
+    }
+
+    ngOnInit(): void {
+        this.route.params.subscribe((params: any) => {
+            if (params['id'] === undefined) {
+                this.creationMode = true;
+                this.title = this.translate.instant('lang.otpConnectorCreation');
+                this.loading = false;
+                this.connectorClone = JSON.parse(JSON.stringify(this.connector));
+            } else {
+                this.creationMode = false;
+
+                this.http.get('../rest/otps/' + params['id'])
+                    .pipe(
+                        map((data: any) => data.otp),
+                        finalize(() => {
+                            this.loading = false;
+                        }),
+                        tap((data: any) => {
+                            this.connector = data;
+                            this.connectorClone = JSON.parse(JSON.stringify(this.connector));
+                            this.title = this.connector.label;
+                        }),
+                        catchError((err: any) => {
+                            this.notificationService.handleErrors(err);
+                            return of(false);
+                        })
+                    )
+                    .subscribe();
+            }
+        });
+    }
+
+    canValidate() {
+        if (this.connector.label === this.connectorClone.label) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    onSubmit() {
+        if (this.creationMode) {
+            this.createconnector();
+        } else {
+            this.modifyconnector();
+        }
+    }
+
+    modifyconnector() {
+        this.loading = true;
+        this.http.put('../rest/connectors/' + this.connector.id, this.connector)
+            .pipe(
+                tap(() => {
+                    this.router.navigate(['/administration/connectors']);
+                    this.notificationService.success('lang.connectorUpdated');
+                }),
+                catchError((err: any) => {
+                    this.notificationService.handleErrors(err);
+                    return of(false);
+                })
+            )
+            .subscribe();
+    }
+
+    createconnector() {
+        this.loading = true;
+        this.http.post('../rest/connectors', this.connector)
+            .pipe(
+                tap((data: any) => {
+                    this.router.navigate(['/administration/connectors/' + data.id]);
+                    this.notificationService.success('lang.connectorAdded');
+                }),
+                catchError((err: any) => {
+                    this.notificationService.handleErrors(err);
+                    return of(false);
+                })
+            )
+            .subscribe();
+    }
+
+    async deleteconnector() {
+        const alert = await this.alertController.create({
+            // cssClass: 'custom-alert-danger',
+            header: this.translate.instant('lang.confirmMsg'),
+            buttons: [
+                {
+                    text: this.translate.instant('lang.no'),
+                    role: 'cancel',
+                    cssClass: 'secondary',
+                    handler: () => { }
+                },
+                {
+                    text: this.translate.instant('lang.yes'),
+                    handler: () => {
+                        this.http.delete('../rest/connectors/' + this.connector.id)
+                            .pipe(
+                                tap(() => {
+                                    this.router.navigate(['/administration/connectors']);
+                                    this.notificationService.success('lang.connectorDeleted');
+                                }),
+                                catchError((err: any) => {
+                                    this.notificationService.handleErrors(err);
+                                    return of(false);
+                                })
+                            )
+                            .subscribe();
+                    }
+                }
+            ]
+        });
+
+        await alert.present();
+    }
+
+    cancel() {
+        this.router.navigate(['/administration/connectors']);
+    }
+
+}
diff --git a/src/frontend/app/app-routing.module.ts b/src/frontend/app/app-routing.module.ts
index d88ca3b5a8809e4aacf81fd3b4a25991122496f4..422c307d241a622f9046a2dfed573d28c19adc64 100755
--- a/src/frontend/app/app-routing.module.ts
+++ b/src/frontend/app/app-routing.module.ts
@@ -22,6 +22,8 @@ import { HomeComponent } from './home/home.component';
 import { IndexationComponent } from './indexation/indexation.component';
 import { SearchComponent } from './search/search.component';
 import { HistoryListComponent } from './administration/history/history-list.component';
+import { OtpListComponent } from './administration/otp/otp-list.component';
+import { OtpComponent } from './administration/otp/otp.component';
 
 @NgModule({
     imports: [
@@ -44,6 +46,9 @@ import { HistoryListComponent } from './administration/history/history-list.comp
             { path: 'administration/emailConfiguration', canActivate: [AuthGuard], component: SendmailComponent },
             { path: 'administration/passwordRules', canActivate: [AuthGuard], component: SecuritiesAdministrationComponent },
             { path: 'administration/history', canActivate: [AuthGuard], component: HistoryListComponent },
+            { path: 'administration/otps', canActivate: [AuthGuard], component: OtpListComponent },
+            { path: 'administration/otps/new', canActivate: [AuthGuard], component: OtpComponent },
+            { path: 'administration/otps/:id', canActivate: [AuthGuard], component: OtpComponent },
             { path: 'documents/:id', canActivate: [AuthGuard], component: DocumentComponent },
             { path: 'login', canActivate: [AuthGuard], component: LoginComponent },
             { path: 'forgot-password', component: ForgotPasswordComponent },
diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts
index 011bd10f7a37278c15584a1b669cf8395648d735..03fd9f9bfd86f2141851dd5f72617e2235c2c94c 100755
--- a/src/frontend/app/app.module.ts
+++ b/src/frontend/app/app.module.ts
@@ -81,6 +81,8 @@ import { GroupComponent } from './administration/group/group.component';
 import { UsersComponent } from './administration/group/list/users.component';
 import { SecuritiesAdministrationComponent } from './administration/security/securities-administration.component';
 import { HistoryListComponent } from './administration/history/history-list.component';
+import { OtpListComponent } from './administration/otp/otp-list.component';
+import { OtpComponent } from './administration/otp/otp.component';
 
 
 // SERVICES
@@ -150,7 +152,9 @@ registerLocaleData(localeFr, 'fr-FR');
         DocumentDateListComponent,
         DateOptionModalComponent,
         OtpCreateComponent,
-        OtpYousignComponent
+        OtpYousignComponent,
+        OtpListComponent,
+        OtpComponent
     ],
     imports: [
         FormsModule,
diff --git a/src/frontend/app/document/visa-workflow/otps/otp.service.ts b/src/frontend/app/document/visa-workflow/otps/otp.service.ts
index 59741d7e798e282e6ca83cd717a7c492c3f3440b..b684283e5ee726a08b25b87d6c8fab8094c241f5 100644
--- a/src/frontend/app/document/visa-workflow/otps/otp.service.ts
+++ b/src/frontend/app/document/visa-workflow/otps/otp.service.ts
@@ -9,12 +9,15 @@ import { NotificationService } from '../../../service/notification.service';
 })
 export class OtpService {
 
+    otpConnectorTypes: string[] = [
+        'yousign'
+    ];
     constructor(
         public http: HttpClient,
         public notificationService: NotificationService,
     ) { }
 
-    getUserOtpIcon(id: string) {
+    getUserOtpIcon(id: string): Promise<string> {
         return new Promise((resolve) => {
             this.http.get(`assets/${id}.png`, { responseType: 'blob' }).pipe(
                 tap((response: any) => {
@@ -31,4 +34,8 @@ export class OtpService {
             ).subscribe();
         });
     }
+
+    getConnectorTypes() {
+        return this.otpConnectorTypes;
+    }
 }