diff --git a/README.md b/README.md
index 05e98b80b025c0cca47a8c7a6c8a935e5f485fa4..af934ff3daa9093e924e25f6dd61ae0776b25ebf 100644
--- a/README.md
+++ b/README.md
@@ -40,12 +40,12 @@ curl -s -D - -H "Accept: application/json"  \
 
 Removes an author
 ```shell
-curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/authors/1"
+curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/auteurs/1"
 ```
 
 Removes all authors
 ```shell
-curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/authors"
+curl -s -D - -X DELETE "http://localhost:9998/myapp/biblio/auteurs"
 ```
 
 Adds an author
@@ -77,8 +77,13 @@ Filter resources with query parameters :
 curl -v -H "Accept: application/json"  \
  "http://127.0.0.1:9998/myapp/biblio/auteurs/filter?nom=Durand&prenom⁼Marie"
 ```
+
 Control sort key with header param (default value "nom") :
 ```shell
 curl -v -H "Accept: application/json"  -H "sortKey: prenom"\
 "http://127.0.0.1:9998/myapp/biblio/auteurs/filter"
+```
+Login and get a Java Web Token
+```shell
+TOKEN=$(curl -v --user "john.doe@nowhere.com:admin" "http://localhost:9998/myapp/biblio/login")
 ```
\ No newline at end of file
diff --git a/queries/sample-requests.rest b/queries/sample-requests.rest
new file mode 100644
index 0000000000000000000000000000000000000000..bfbf655972055a7acf1cd1aeed2d4a0e14c82c6a
--- /dev/null
+++ b/queries/sample-requests.rest
@@ -0,0 +1,101 @@
+# curl -D - http://localhost:9998/myapp/biblio
+### Get a Hello message
+GET http://localhost:9998/myapp/biblio
+
+### Init the database with two authors
+PUT http://localhost:9998/myapp/biblio/init
+
+### Get author 1 in JSON
+GET http://localhost:9998/myapp/biblio/auteurs/1
+Accept: application/json
+
+### Get author 2 in XML
+GET http://localhost:9998/myapp/biblio/auteurs/2
+Accept: text/xml
+
+### Get authors in JSON
+GET http://localhost:9998/myapp/biblio/auteurs
+Accept: application/json
+
+### Removes an author
+DELETE http://localhost:9998/myapp/biblio/auteurs/1
+
+### Removes all authors
+DELETE http://localhost:9998/myapp/biblio/auteurs
+
+### Adds an author
+POST http://localhost:9998/myapp/biblio/auteurs/
+Accept: application/json
+Content-type: application/json
+
+{"nom":"John","prenom":"Smith","biographie":"My life"}
+
+### Fully update an author
+PUT http://localhost:9998/myapp/biblio/auteurs/1
+Accept: application/json
+Content-type: application/json
+
+{"nom":"Martin","prenom":"Jean","biographie":"ma vie"}
+
+### If a resource doesn't exist an exception is raised, and the 404 http status code is returned
+GET http://localhost:9998/myapp/biblio/auteurs/1000
+Accept: application/json
+
+### If a resource doesn't exist an exception is raised, and the 404 http status code is returned
+GET http://localhost:9998/myapp/biblio/auteurs/1000
+Accept: text/xml
+
+### Filter resources with query parameters :
+GET http://localhost:9998/myapp/biblio/auteurs/filter?nom=Durand&prenom⁼Marie
+Accept: application/json
+
+### Control sort key with header param (default value "nom") :
+GET http://127.0.0.1:9998/myapp/biblio/auteurs/filter
+Accept: application/json
+sortKey: prenom
+
+### Init the database with 10k random authors
+PUT http://localhost:9998/myapp/biblio/init/10000
+
+### Get page 3 with page size of 10 authors sorted by lastname
+GET http://localhost:9998/myapp/biblio/auteurs/page?pageSize=10&page=3
+Accept: application/json
+sortKey: prenom
+
+### Returns the context of the query (without authentication).
+GET http://localhost:9998/myapp/biblio/context
+biblio-demo-header-1: myvalue
+biblio-demo-header-2: anothervalue
+
+### Authorization by token, part 1. Retrieve and save token with Basic Authentication
+#  TOKEN=$(curl -v --user "john.doe@nowhere.com:admin" "http://localhost:9998/myapp/biblio/login")
+GET http://localhost:9998/myapp/biblio/login
+Authorization: Basic john.doe@nowhere.com admin
+
+> {% client.global.set("auth_token", response.body); %}
+
+### Authorization by token, part 2. Use token to authorize. Admin & User OK
+# curl -H "Authorization: Bearer $TOKEN" -v "http://localhost:9998/myapp/biblio/secured"
+GET http://localhost:9998/myapp/biblio/secured
+Authorization: Bearer {{auth_token}}
+
+### Authorization by token, part 2. Use token to authorize. Admin OK
+GET http://localhost:9998/myapp/biblio/secured/admin
+Authorization: Bearer {{auth_token}}
+
+### Authorization with another user.
+#  TOKEN=$(curl -v --user "mary.roberts@here.net:user" "http://localhost:9998/myapp/biblio/login")
+GET http://localhost:9998/myapp/biblio/login
+Authorization: Basic mary.roberts@here.net user
+
+> {% client.global.set("auth_token", response.body); %}
+
+### Authorization by token, part 2. Use token to authorize. Admin & User OK.
+# curl -H "Authorization: Bearer $TOKEN" -v "http://localhost:9998/myapp/biblio/secured"
+GET http://localhost:9998/myapp/biblio/secured
+Authorization: Bearer {{auth_token}}
+
+### Authorization by token, part 2. Use token to authorize. Admin KO.
+GET http://localhost:9998/myapp/biblio/secured/admin
+Authorization: Bearer {{auth_token}}
+
diff --git a/sonar.sh b/sonar.sh
index 1c440608e5f82480044032fdefd895f6189366d1..d93fa7ea162627a0414f2b57aba9a8012d53c139 100755
--- a/sonar.sh
+++ b/sonar.sh
@@ -1 +1 @@
-mvn sonar:sonar -D sonar.branch.name="$(git rev-parse --abbrev-ref HEAD|tr / _ )" -DskipTests=true -Dsonar.language=java -Dsonar.report.export.path=sonar-report.json -Dsonar.host.url=http://localhost:9000 --activate-profiles sonar
+mvn sonar:sonar -D sonar.branch.name="$(git rev-parse --abbrev-ref HEAD)" -DskipTests=true -Dsonar.language=java -Dsonar.report.export.path=sonar-report.json -Dsonar.host.url=http://localhost:9000 --activate-profiles sonar
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/client/BiblioClient.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/client/BiblioClient.java
index 903059bdaddfabfc2ca7fd4f0f948f178d7366a0..6bdaf72af1ebd4695329856ba219b142083f171f 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/client/BiblioClient.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/client/BiblioClient.java
@@ -34,5 +34,26 @@ public class BiblioClient {
                 .request()
                 .get(Auteur.class);
         log.info(auteur.toString());
+
+        //Log in to get the token with basci authentication
+        String email = "john.doe@nowhere.com";
+        String password = "admin";
+        String token = webResource.path("biblio/login")
+                .request()
+                .accept(MediaType.TEXT_PLAIN)
+                .header("Authorization", "Basic " + java.util.Base64.getEncoder().encodeToString((email + ":" + password).getBytes()))
+                .get(String.class);
+        if (!token.isBlank()) {
+            log.info("token received.");
+            //We access a JWT protected URL with the token
+            String result = webResource.path("biblio/secured")
+                    .request()
+                    .header("Authorization", "Bearer " + token)
+                    .get(String.class);
+
+            log.info(result);
+        }
+
+
     }
 }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/BusinessException.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/BusinessException.java
index ed7138b016b48f1ddd03e93ae8515bc772395bf8..fa83532d978ffef37ada52f7627f791b2261cc70 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/BusinessException.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/BusinessException.java
@@ -1,12 +1,32 @@
 package fr.univtln.bruno.samples.jaxrs.exceptions;
 
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import jakarta.ws.rs.core.Response;
+import jakarta.xml.bind.annotation.XmlRootElement;
 import lombok.Getter;
 
+import java.io.Serializable;
+
+/**
+ * The type Business exception, used add HTTP (HATEOS) capacities to exceptions.
+ */
 @Getter
-public class BusinessException extends Exception {
+@JsonIgnoreProperties({"stackTrace"})
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+
+@XmlRootElement
+public class BusinessException extends Exception implements Serializable {
+    /**
+     * The Status.
+     */
     final Response.Status status;
 
+    /**
+     * Instantiates a new Business exception.
+     *
+     * @param status the status
+     */
     public BusinessException(Response.Status status) {
         super(status.getReasonPhrase());
         this.status = status;
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/IllegalArgumentException.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/IllegalArgumentException.java
index 7a8e69d1bb7286cfb36119731c6b9838e7aabb9e..deae099b7181c328ecf1bdccf0857166e424c4f5 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/IllegalArgumentException.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/IllegalArgumentException.java
@@ -1,6 +1,6 @@
 package fr.univtln.bruno.samples.jaxrs.exceptions;
 
-import static jakarta.ws.rs.core.Response.*;
+import static jakarta.ws.rs.core.Response.Status;
 
 public class IllegalArgumentException extends BusinessException {
     public IllegalArgumentException() {
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/NotFoundException.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/NotFoundException.java
index 64f65a1fd75ebcf230ae4bbc71f3da0eca79fd1d..f28fc2544258ebce1852ec5dbcf668989ae01892 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/NotFoundException.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/exceptions/NotFoundException.java
@@ -1,7 +1,9 @@
 package fr.univtln.bruno.samples.jaxrs.exceptions;
 
 import jakarta.ws.rs.core.Response;
+import jakarta.xml.bind.annotation.XmlRootElement;
 
+@XmlRootElement
 public class NotFoundException extends BusinessException {
     public NotFoundException() {
         super(Response.Status.NOT_FOUND);
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/BusinessExceptionMapper.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/BusinessExceptionMapper.java
index 8ddb6787412b73453c99851ae21b9fe9500c0691..a057821d46434c40ee0559cfff548a73baf205e5 100755
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/BusinessExceptionMapper.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/BusinessExceptionMapper.java
@@ -1,22 +1,21 @@
 package fr.univtln.bruno.samples.jaxrs.mappers;
 
 import fr.univtln.bruno.samples.jaxrs.exceptions.BusinessException;
-import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Provider;
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
+import lombok.extern.java.Log;
 
 @SuppressWarnings("unused")
 @Provider
 @FieldDefaults(level = AccessLevel.PRIVATE)
+@Log
 public class BusinessExceptionMapper implements ExceptionMapper<BusinessException> {
-
     public Response toResponse(BusinessException ex) {
         return Response.status(ex.getStatus())
-                .entity(ex.getMessage())
-                .type(MediaType.APPLICATION_JSON)
+                .entity(ex)
                 .build();
     }
 }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/GenericExceptionMapper.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/GenericExceptionMapper.java
index 6902906eb8f458dba24a1d4034470eee5e4bb148..554394ad53ee603c1dc9867a5914e7cc077b3c7a 100755
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/GenericExceptionMapper.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/mappers/GenericExceptionMapper.java
@@ -6,10 +6,12 @@ import jakarta.ws.rs.ext.ExceptionMapper;
 import jakarta.ws.rs.ext.Provider;
 import lombok.AccessLevel;
 import lombok.experimental.FieldDefaults;
+import lombok.extern.java.Log;
 
 @SuppressWarnings("unused")
 @Provider
 @FieldDefaults(level = AccessLevel.PRIVATE)
+@Log
 public class GenericExceptionMapper implements ExceptionMapper<Exception> {
     public Response toResponse(Exception ex) {
         return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
index a1386c3e07ed48c6544fc2922e4f23ece80818e7..ff787698d1e8fa944a70ac3e659936b5bf855078 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/model/BiblioModel.java
@@ -3,15 +3,11 @@ package fr.univtln.bruno.samples.jaxrs.model;
 import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException;
 import fr.univtln.bruno.samples.jaxrs.exceptions.NotFoundException;
 import fr.univtln.bruno.samples.jaxrs.resources.PaginationInfo;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.core.*;
 import jakarta.xml.bind.annotation.XmlAccessType;
 import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import lombok.*;
-import lombok.experimental.Delegate;
 import lombok.experimental.FieldDefaults;
 import lombok.extern.java.Log;
 import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
@@ -27,16 +23,26 @@ import java.util.stream.Stream;
 
 import static fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Field.valueOf;
 
+
+/**
+ * The type Biblio model. A in memory instance of a Library model. Kind of a mock.
+ */
 @Log
 @Getter
 @FieldDefaults(level = AccessLevel.PRIVATE)
 @NoArgsConstructor(staticName = "of")
 public class BiblioModel {
-    private static AtomicLong lastId = new AtomicLong(0);
-
-    @Delegate
+    private static final AtomicLong lastId = new AtomicLong(0);
+    //@Delegate
     final MutableLongObjectMap<Auteur> auteurs = LongObjectMaps.mutable.empty();
 
+    /**
+     * Add auteur auteur.
+     *
+     * @param auteur the auteur
+     * @return the auteur
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     public Auteur addAuteur(Auteur auteur) throws IllegalArgumentException {
         if (auteur.id != 0) throw new IllegalArgumentException();
         auteur.id = lastId.incrementAndGet();
@@ -44,6 +50,15 @@ public class BiblioModel {
         return auteur;
     }
 
+    /**
+     * Update auteur auteur by id and data contains in a author instance (except the id).
+     *
+     * @param id     the id
+     * @param auteur the auteur
+     * @return the auteur
+     * @throws NotFoundException        the not found exception
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     public Auteur updateAuteur(long id, Auteur auteur) throws NotFoundException, IllegalArgumentException {
         if (auteur.id != 0) throw new IllegalArgumentException();
         auteur.id = id;
@@ -52,33 +67,62 @@ public class BiblioModel {
         return auteur;
     }
 
+    /**
+     * Remove one auteur by id.
+     *
+     * @param id the id
+     * @throws NotFoundException the not found exception
+     */
     public void removeAuteur(long id) throws NotFoundException {
         if (!auteurs.containsKey(id)) throw new NotFoundException();
         auteurs.remove(id);
     }
 
+    /**
+     * Gets one auteur id.
+     *
+     * @param id the id
+     * @return the auteur
+     * @throws NotFoundException the not found exception
+     */
     public Auteur getAuteur(long id) throws NotFoundException {
         if (!auteurs.containsKey(id)) throw new NotFoundException();
         return auteurs.get(id);
     }
 
+    /**
+     * Gets the number of authors.
+     *
+     * @return the auteur size
+     */
     public int getAuteurSize() {
         return auteurs.size();
     }
 
+    /**
+     * Returns a sorted, filtered and paginated list of authors.
+     *
+     * @param paginationInfo the pagination info
+     * @return the sorted, filtered page.
+     */
     public List<Auteur> getWithFilter(PaginationInfo paginationInfo) {
+        //We build a author stream, first we add sorting
         Stream<Auteur> auteurStream = auteurs.stream()
                 .sorted(Comparator.comparing(auteur -> switch (valueOf(paginationInfo.getSortKey().toUpperCase())) {
                     case NOM -> auteur.getNom();
                     case PRENOM -> auteur.getPrenom();
                     default -> throw new InvalidParameterException();
                 }));
+
+        //The add filters according to parameters
         if (paginationInfo.getNom() != null)
             auteurStream = auteurStream.filter(auteur -> auteur.getNom().equalsIgnoreCase(paginationInfo.getNom()));
         if (paginationInfo.getPrenom() != null)
             auteurStream = auteurStream.filter(auteur -> auteur.getPrenom().equalsIgnoreCase(paginationInfo.getPrenom()));
         if (paginationInfo.getBiographie() != null)
             auteurStream = auteurStream.filter(auteur -> auteur.getBiographie().contains(paginationInfo.getBiographie()));
+
+        //Finally add pagination instructions.
         if ((paginationInfo.getPage() > 0) && (paginationInfo.getPageSize() > 0)) {
             auteurStream = auteurStream
                     .skip(paginationInfo.getPageSize() * (paginationInfo.getPage() - 1))
@@ -88,13 +132,32 @@ public class BiblioModel {
         return auteurStream.collect(Collectors.toList());
     }
 
+    /**
+     * Removes all authors.
+     */
     public void supprimerAuteurs() {
         auteurs.clear();
         lastId.set(0);
     }
 
-    public enum Field {NOM, PRENOM, BIOGRAPHIE}
+    /**
+     * The list of fields of author that can used in filters.
+     */
+    public enum Field {
+        NOM,
+        /**
+         * Prenom field.
+         */
+        PRENOM,
+        /**
+         * Biographie field.
+         */
+        BIOGRAPHIE
+    }
 
+    /**
+     * The type Author.
+     */
     @Builder
     @Getter
     @Setter
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
index 13596286d94cb7052becf09e9fdb12b7cc8e1f40..8cafd62722b53e0a344f08d651dc57478c7e933b 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/BiblioResource.java
@@ -5,10 +5,10 @@ import fr.univtln.bruno.samples.jaxrs.exceptions.IllegalArgumentException;
 import fr.univtln.bruno.samples.jaxrs.exceptions.NotFoundException;
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel;
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Auteur;
-import fr.univtln.bruno.samples.jaxrs.security.BasicAuth;
-import fr.univtln.bruno.samples.jaxrs.security.JWTAuth;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import fr.univtln.bruno.samples.jaxrs.security.User;
-import fr.univtln.bruno.samples.jaxrs.security.UserDatabase;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.JWTAuth;
 import fr.univtln.bruno.samples.jaxrs.status.Status;
 import io.jsonwebtoken.Jwts;
 import jakarta.annotation.security.RolesAllowed;
@@ -18,23 +18,33 @@ import lombok.extern.java.Log;
 
 import javax.naming.AuthenticationException;
 import java.security.SecureRandom;
-import java.sql.Date;
-import java.text.ParseException;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
+/**
+ * The Biblio resource.
+ * A demo JAXRS class, that manages authors and offers a secured access.
+ */
 @Log
 // The Java class will be hosted at the URI path "/biblio"
 @Path("biblio")
 @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
 public class BiblioResource {
+    //A in memory instance of a Library model. Kind of a mock.
     private static final BiblioModel modeleBibliotheque = BiblioModel.of();
 
+    //A random number generator
     private static final SecureRandom random = new SecureRandom();
 
+    /**
+     * The simpliest method that just return "hello" in plain text with GET on the default path "biblio".
+     *
+     * @return the string
+     */
     @SuppressWarnings("SameReturnValue")
     @GET
     @Produces(MediaType.TEXT_PLAIN)
@@ -42,6 +52,12 @@ public class BiblioResource {
         return "hello";
     }
 
+    /**
+     * An init method that add two authors with a PUT on the default path.
+     *
+     * @return the number of generated authors.
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     @PUT
     @Path("init")
     public int init() throws IllegalArgumentException {
@@ -51,6 +67,15 @@ public class BiblioResource {
         return modeleBibliotheque.getAuteurSize();
     }
 
+    /**
+     * An init method that add a given number of random authors whose names are just random letters on PUT.
+     * The number of authors if given in the path avec bound to the name size. The needed format (an integer) is checked with a regular expression [0-9]+
+     * The parameter is injected with @PathParam
+     *
+     * @param size the number of authors to add
+     * @return the int number of generated authors.
+     * @throws IllegalArgumentException the illegal argument exception
+     */
     @PUT
     @Path("init/{size:[0-9]+}")
     public int init(@PathParam("size") int size) throws IllegalArgumentException {
@@ -63,17 +88,30 @@ public class BiblioResource {
         return modeleBibliotheque.getAuteurSize();
     }
 
+    /**
+     * A random string generator
+     *
+     * @param targetStringLength the length of the String
+     * @return
+     */
     private String randomString(int targetStringLength) {
         int letterA = 97;
         int letterZ = 122;
-
-
         return random.ints(letterA, letterZ + 1)
                 .limit(targetStringLength)
                 .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
                 .toString();
     }
 
+    /**
+     * Update an author with an given id.
+     *
+     * @param id     the id injected from the path param "id"
+     * @param auteur a injected author made from the JSON data (@Consumes) from body of the request. This author is forbidden to havce an Id.
+     * @return The resulting author with its id.
+     * @throws NotFoundException        is returned if no author has the "id".
+     * @throws IllegalArgumentException is returned if an "id" is also given in the request body.
+     */
     @PUT
     @Path("auteurs/{id}")
     @Consumes(MediaType.APPLICATION_JSON)
@@ -82,11 +120,12 @@ public class BiblioResource {
     }
 
     /**
+     * Adds an new author to the data.
      * Status annotation is a trick to fine tune 2XX status codes (see the status package).
      *
      * @param auteur The author to be added without its id.
      * @return The added author with its id.
-     * @throws IllegalArgumentException
+     * @throws IllegalArgumentException if the author has an explicit id (id!=0).
      */
     @POST
     @Status(Status.CREATED)
@@ -96,30 +135,61 @@ public class BiblioResource {
         return modeleBibliotheque.addAuteur(auteur);
     }
 
+    /**
+     * Removes an author by id from the data.
+     *
+     * @param id the id of the author to remove
+     * @throws NotFoundException is returned if no author has the "id".
+     */
     @DELETE
     @Path("auteurs/{id}")
     public void supprimerAuteur(@PathParam("id") final long id) throws NotFoundException {
         modeleBibliotheque.removeAuteur(id);
     }
 
+    /**
+     * Removes every authors
+     */
     @DELETE
     @Path("auteurs")
     public void supprimerAuteurs() {
         modeleBibliotheque.supprimerAuteurs();
     }
 
+    /**
+     * Find and return an author by id with a GET on the path "biblio/auteurs/{id}" where  {id} is the needed id.
+     * The path parameter "id" is injected with @PathParam.
+     *
+     * @param id the needed author id.
+     * @return the auteur with id.
+     * @throws NotFoundException is returned if no author has the "id".
+     */
     @GET
     @Path("auteurs/{id}")
     public Auteur getAuteur(@PathParam("id") final long id) throws NotFoundException {
         return modeleBibliotheque.getAuteur(id);
     }
 
+    /**
+     * Gets auteurs.
+     *
+     * @return the auteurs
+     */
     @GET
     @Path("auteurs")
     public Collection<Auteur> getAuteurs() {
         return modeleBibliotheque.getAuteurs().values();
     }
 
+    /**
+     * Gets a list of "filtered" authors.
+     *
+     * @param nom        an optional exact filter on the name.
+     * @param prenom     an optional exact filter on the firstname.
+     * @param biographie an optional contains filter on the biography.
+     * @param sortKey    the sort key (prenom or nom).
+     * @return the filtered auteurs
+     */
     @GET
     @Path("auteurs/filter")
     public List<Auteur> getFilteredAuteurs(@QueryParam("nom") String nom,
@@ -136,53 +206,117 @@ public class BiblioResource {
         return modeleBibliotheque.getWithFilter(paginationInfo);
     }
 
+    /**
+     * Gets a page of authors after applying a sort.
+     *
+     * @param paginationInfo the pagination info represented as a class injected with @BeanParam.
+     * @return the page of authors.
+     */
     @GET
     @Path("auteurs/page")
     public List<Auteur> getAuteursPage(@BeanParam PaginationInfo paginationInfo) {
         return modeleBibliotheque.getWithFilter(paginationInfo);
     }
 
+    /**
+     * A GET method to access the context of the request : The URI, the HTTP headers, the request and the security context (needs authentication see below).
+     *
+     * @param uriInfo         the uri info
+     * @param httpHeaders     the http headers
+     * @param request         the request
+     * @param securityContext the security context
+     * @return A string representation of the available data.
+     */
     @GET
     @Path("context")
-    @RolesAllowed("ADMIN")
-    public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) throws ParseException {
-        return "UriInfo: (" + uriInfo.getRequestUri().toString()
-               + ")\n HttpHeaders(" + httpHeaders.getRequestHeaders().toString()
-               //+")\n Request Precondition("+request.evaluatePreconditions(new SimpleDateFormat("dd/MM/yyyy-HH:mm:ss").parse("03/02/2021-10:30:00"))
-               + ")\n SecurityContext(Auth.scheme: [" + securityContext.getAuthenticationScheme()
-               + "] user: [" + securityContext.getUserPrincipal().getName()
-               + "] secured: [" + securityContext.isSecure() + "] )";
+    public String getContext(@Context UriInfo uriInfo, @Context HttpHeaders httpHeaders, @Context Request request, @Context SecurityContext securityContext) {
+        String result = "UriInfo: (" + uriInfo.getRequestUri().toString() + ")\n"
+                        + "Method: ("+request.getMethod()+")\n"
+                        + "HttpHeaders(" + httpHeaders.getRequestHeaders().toString() + ")\n";
+
+        if (securityContext != null) {
+            result += " SecurityContext(Auth.scheme: [" + securityContext.getAuthenticationScheme() + "] \n";
+            if (securityContext.getUserPrincipal() != null)
+                result += "    user: [" + securityContext.getUserPrincipal().getName() + "] \n";
+            result += "    secured: [" + securityContext.isSecure() + "] )";
+        }
+        return result;
     }
 
+    /**
+     * A GET restricted to ADMIN role with basic authentication.
+     * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter
+     *
+     * @param securityContext the security context
+     * @return the restricted to admins
+     */
     @GET
     @Path("adminsonly")
     @RolesAllowed("ADMIN")
     @BasicAuth
-    public String getRestrictedToAdmins() {
-        return "secret for admins !";
+    public String getRestrictedToAdmins(@Context SecurityContext securityContext) {
+        return "secret for admins !" + securityContext.getUserPrincipal().getName();
     }
 
+    /**
+     * A GET restricted to USER role with basic authentication (and not ADMIN !).
+     * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter
+     *
+     * @param securityContext the security context
+     * @return the restricted to users
+     */
     @GET
     @Path("usersonly")
     @RolesAllowed("USER")
     @BasicAuth
-    public String getRestrictedToUsers() {
-        return "secret for users !";
+    public String getRestrictedToUsers(@Context SecurityContext securityContext) {
+        return "secret for users ! to " + securityContext.getUserPrincipal().getName();
     }
 
+    /**
+     * A GET restricted to USER & ADMIN roles, secured with a JWT Token.
+     * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter
+     *
+     * @param securityContext the security context
+     * @return the string
+     */
     @GET
     @Path("secured")
     @RolesAllowed({"USER", "ADMIN"})
     @JWTAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
     public String securedByJWT(@Context SecurityContext securityContext) {
-        log.info("USER ACCESS :"+securityContext.getUserPrincipal().getName());
-        return "Access with JWT ok for "+securityContext.getUserPrincipal().getName();
+        log.info("USER ACCESS :" + securityContext.getUserPrincipal().getName());
+        return "Access with JWT ok for " + securityContext.getUserPrincipal().getName();
+    }
+
+    /**
+     * A GET restricted to ADMIN roles, secured with a JWT Token.
+     *
+     * @param securityContext the security context
+     * @return the string
+     */
+    @GET
+    @Path("secured/admin")
+    @RolesAllowed({"ADMIN"})
+    @JWTAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
+    public String securedByJWTAdminOnly(@Context SecurityContext securityContext) {
+        log.info("ADMIN ACCESS :" + securityContext.getUserPrincipal().getName());
+        return "Access with JWT ok for " + securityContext.getUserPrincipal().getName();
     }
 
+    /**
+     * a GET method to obtain a JWT token with basic authentication for USER and ADMIN roles.
+     *
+     * @param securityContext the security context
+     * @return the base64 encoded JWT Token.
+     */
     @GET
     @Path("login")
     @RolesAllowed({"USER", "ADMIN"})
     @BasicAuth
+    @Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
     public String login(@Context SecurityContext securityContext) {
         if (securityContext.isSecure() && securityContext.getUserPrincipal() instanceof User) {
             User user = (User) securityContext.getUserPrincipal();
@@ -194,7 +328,7 @@ public class BiblioResource {
                     .claim("lastname", user.getLastName())
                     .claim("roles", user.getRoles())
                     .setExpiration(Date.from(LocalDateTime.now().plus(15, ChronoUnit.MINUTES).atZone(ZoneId.systemDefault()).toInstant()))
-                    .signWith(UserDatabase.KEY).compact();
+                    .signWith(InMemoryLoginModule.KEY).compact();
         }
         throw new WebApplicationException(new AuthenticationException());
     }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
index 6e4c2cd65cc28d59a2153773a2b6293247b196e4..bc9c399dccbe71390b404529edbe4d86edbba419 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/resources/PaginationInfo.java
@@ -6,6 +6,10 @@ import jakarta.ws.rs.QueryParam;
 import lombok.*;
 import lombok.experimental.FieldDefaults;
 
+/**
+ * The Pagination information to be injected with @BeanPararm Filter Queries.
+ * Each field is annotated with a JAX-RS parameter injection.
+ */
 @FieldDefaults(level = AccessLevel.PRIVATE)
 @Getter
 @ToString
@@ -13,18 +17,20 @@ import lombok.experimental.FieldDefaults;
 @NoArgsConstructor
 @AllArgsConstructor
 public class PaginationInfo {
-    @HeaderParam("sortKey")
-    @DefaultValue("nom")
-    String sortKey;
-
+    @SuppressWarnings("FieldMayBeFinal")
     @QueryParam("page")
     @Builder.Default
     long page = 1;
 
+    @SuppressWarnings("FieldMayBeFinal")
     @QueryParam("pageSize")
     @Builder.Default
     long pageSize = 10;
 
+    @HeaderParam("sortKey")
+    @DefaultValue("nom")
+    String sortKey;
+
     @QueryParam("nom")
     String nom;
 
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..6245c8bd9bf73e31dae4c22c6a515006692d5e97
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/InMemoryLoginModule.java
@@ -0,0 +1,138 @@
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+import lombok.AccessLevel;
+import lombok.ToString;
+import lombok.experimental.FieldDefaults;
+import lombok.extern.java.Log;
+
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.*;
+
+/**
+ * this class model a simple in memory role based authentication database (RBAC).
+ * Password are salted and hashed.
+ */
+@Log
+@ToString
+@FieldDefaults(level = AccessLevel.PRIVATE)
+public class InMemoryLoginModule {
+    /**
+     * The constant USER_DATABASE mocks a user database in memory.
+     */
+    public static final InMemoryLoginModule USER_DATABASE = new InMemoryLoginModule();
+
+    /**
+     * The constant KEY is used as a signing key for the bearer JWT token.
+     * It is used to check that the token hasn't been modified.
+     */
+    public static final Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
+
+    //We add three demo users.
+    static {
+        try {
+            USER_DATABASE.addUser("John", "Doe", "john.doe@nowhere.com", "admin", EnumSet.of(Role.ADMIN));
+            USER_DATABASE.addUser("William", "Smith", "william.smith@here.net", "user", EnumSet.of(Role.USER));
+            USER_DATABASE.addUser("Mary", "Robert", "mary.roberts@here.net", "user", EnumSet.of(Role.USER));
+        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
+            log.severe("In memory user database error "+e.getLocalizedMessage());
+        }
+    }
+
+    final Map<String, User> users = new HashMap<>();
+
+    public static boolean isInRoles(Set<Role> rolesSet, String username) {
+        return !(Collections.disjoint(rolesSet, InMemoryLoginModule.USER_DATABASE.getUserRoles(username)));
+    }
+
+    /**
+     * Add user.
+     *
+     * @param firstname the firstname
+     * @param lastname  the lastname
+     * @param email     the email
+     * @param password  the password
+     * @param roles     the roles
+     * @throws InvalidKeySpecException  the invalid key spec exception
+     * @throws NoSuchAlgorithmException the no such algorithm exception
+     */
+    public void addUser(String firstname, String lastname, String email, String password, Set<Role> roles)
+            throws InvalidKeySpecException, NoSuchAlgorithmException {
+        users.put(email, User.builder().firstName(firstname).lastName(lastname).email(email).password(password).roles(roles).build());
+    }
+
+    /**
+     * Gets users.
+     *
+     * @return the users
+     */
+    public Map<String, User> getUsers() {
+        return Collections.unmodifiableMap(users);
+    }
+
+    /**
+     * Remove user.
+     *
+     * @param email the email
+     */
+    public void removeUser(String email) {
+        users.remove(email);
+    }
+
+    /**
+     * Check password boolean.
+     *
+     * @param email    the email
+     * @param password the password
+     * @return the boolean
+     */
+    public boolean login(String email, String password) {
+        return users.get(email).checkPassword(password);
+    }
+
+    /**
+     * Gets user.
+     *
+     * @param email the email
+     * @return the user
+     */
+    public User getUser(String email) {
+        return users.get(email);
+    }
+
+    /**
+     * Gets user roles.
+     *
+     * @param email the email
+     * @return the user roles
+     */
+    public Set<Role> getUserRoles(String email) {
+        return users.get(email).getRoles();
+    }
+
+    @SuppressWarnings("SameReturnValue")
+    public boolean logout() {
+        return false;
+    }
+
+    /**
+     * The enum Role.
+     */
+    public enum Role {
+        /**
+         * Admin role.
+         */
+        ADMIN,
+        /**
+         * User role.
+         */
+        USER,
+        /**
+         * Guest role.
+         */
+        GUEST
+    }
+}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java
deleted file mode 100644
index 1d19aeb0fea9a9b2073db51961f3e86f252270ea..0000000000000000000000000000000000000000
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JsonWebTokenFilter.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package fr.univtln.bruno.samples.jaxrs.security;
-
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jws;
-import io.jsonwebtoken.Jwts;
-import jakarta.annotation.Priority;
-import jakarta.annotation.security.DenyAll;
-import jakarta.annotation.security.PermitAll;
-import jakarta.annotation.security.RolesAllowed;
-import jakarta.ws.rs.Priorities;
-import jakarta.ws.rs.container.ContainerRequestContext;
-import jakarta.ws.rs.container.ContainerRequestFilter;
-import jakarta.ws.rs.container.ResourceInfo;
-import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
-import jakarta.ws.rs.ext.Provider;
-import lombok.SneakyThrows;
-import lombok.extern.java.Log;
-
-import java.lang.reflect.Method;
-import java.security.Principal;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.stream.Collectors;
-
-@JWTAuth
-@Provider
-@Priority(Priorities.AUTHENTICATION)
-@Log
-/**
- * This class if a filter for JAX-RS to perform authentication via JWT.
- */
-public class JsonWebTokenFilter implements ContainerRequestFilter {
-    private static final String AUTHORIZATION_PROPERTY = "Authorization";
-    private static final String AUTHENTICATION_SCHEME = "Bearer";
-
-    //We inject the data from the acceded resource.
-    @Context
-    private ResourceInfo resourceInfo;
-
-    @SneakyThrows
-    @Override
-    public void filter(ContainerRequestContext requestContext) {
-        //We use reflection on the acceded method to look for security annotations.
-        Method method = resourceInfo.getResourceMethod();
-        //if its PermitAll access is granted
-        //otherwise if its DenyAll the access is refused
-        if (!method.isAnnotationPresent(PermitAll.class)) {
-            if (method.isAnnotationPresent(DenyAll.class)) {
-                requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
-                        .entity("Access denied to all users").build());
-                return;
-            }
-
-            //We get the authorization header
-            final String authorization = requestContext.getHeaderString(AUTHORIZATION_PROPERTY);
-
-
-            if (method.isAnnotationPresent(RolesAllowed.class)) {
-                RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
-                EnumSet<UserDatabase.Role> rolesSet =
-                        Arrays.stream(rolesAnnotation.value())
-                                .map(r -> UserDatabase.Role.valueOf(r))
-                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserDatabase.Role.class)));
-
-
-                //We check the credentials presence
-                if (authorization == null || authorization.isEmpty()) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Please provide your credentials").build());
-                    return;
-                }
-
-                //Gets the token
-                log.info("AUTH: "+authorization);
-                final String compactJwt = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
-                if (!authorization.contains(AUTHENTICATION_SCHEME) || compactJwt == null || compactJwt.isEmpty()) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Please provide your credentials").build());
-                    return;
-                }
-                log.info("JWT: "+compactJwt);
-
-                Jws<Claims> jws = Jwts.parserBuilder()
-                        .setSigningKey(UserDatabase.KEY)
-                        .build()
-                        .parseClaimsJws(compactJwt);
-                log.info("JWT decoded: "+jws.toString());
-
-                final String username = jws.getBody().getSubject();
-
-                //We check if the role is allowed
-                if (Collections.disjoint(rolesSet, UserDatabase.USER_DATABASE.getUserRoles(username))) {
-                    requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
-                            .entity("Roles not allowed").build());
-                    return;
-                }
-
-                //We build a new securitycontext to transmit the security data to JAX-RS
-                requestContext.setSecurityContext(new SecurityContext() {
-
-                    @Override
-                    public Principal getUserPrincipal() {
-                        return UserDatabase.USER_DATABASE.getUser(username);
-                    }
-
-                    @Override
-                    public boolean isUserInRole(String role) {
-                        return UserDatabase.USER_DATABASE.getUserRoles(username).contains(UserDatabase.Role.valueOf(role));
-                    }
-
-                    @Override
-                    public boolean isSecure() {
-                        return true;
-                    }
-
-                    @Override
-                    public String getAuthenticationScheme() {
-                        return AUTHENTICATION_SCHEME;
-                    }
-                });
-
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..1ab16f818c33ef4d37e0317160dfdd3929bb9fbc
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/MySecurityContext.java
@@ -0,0 +1,45 @@
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import jakarta.ws.rs.core.SecurityContext;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+import java.security.Principal;
+
+/**
+ * This class define a specific security context after an authentication with either the basic or the JWT filters.
+ *
+ * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter
+ * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter
+ */
+@FieldDefaults(level = AccessLevel.PRIVATE)
+@AllArgsConstructor(staticName = "newInstance")
+public class MySecurityContext implements SecurityContext {
+    private final String authenticationScheme;
+    private final String username;
+
+    //the authenticated user
+    @Override
+    public Principal getUserPrincipal() {
+        return InMemoryLoginModule.USER_DATABASE.getUser(username);
+    }
+
+    //A method to check if a user belongs to a role
+    @Override
+    public boolean isUserInRole(String role) {
+        return InMemoryLoginModule.USER_DATABASE.getUserRoles(username).contains(InMemoryLoginModule.Role.valueOf(role));
+    }
+
+    //Say the access has been secured
+    @Override
+    public boolean isSecure() {
+        return true;
+    }
+
+    //The authentication scheme (Basic, JWT, ...) that has been used.
+    @Override
+    public String getAuthenticationScheme() {
+        return authenticationScheme;
+    }
+}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
index a18f358ebe052175de82944a4ba9f18e935b39b4..6ea65b190be52fe4d00250db86f0aa38322753c7 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/User.java
@@ -23,17 +23,19 @@ import java.util.*;
 @EqualsAndHashCode(of = "email")
 public class User implements Principal {
     UUID uuid = UUID.randomUUID();
-    String firstName, lastName, email;
+    String firstName;
+    String lastName;
+    String email;
     byte[] passwordHash;
     byte[] salt = new byte[16];
 
     @Delegate
-    EnumSet<UserDatabase.Role> roles;
+    Set<InMemoryLoginModule.Role> roles;
 
     SecureRandom random = new SecureRandom();
 
     @Builder
-    public User(String firstName, String lastName, String email, String password, EnumSet<UserDatabase.Role> roles)
+    public User(String firstName, String lastName, String email, String password, Set<InMemoryLoginModule.Role> roles)
             throws NoSuchAlgorithmException, InvalidKeySpecException {
         this.firstName = firstName;
         this.lastName = lastName;
@@ -55,7 +57,8 @@ public class User implements Principal {
         return email + "" + Base64.getEncoder().encodeToString(passwordHash);
     }
 
-    public boolean checkPassword(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
+    @SneakyThrows
+    public boolean checkPassword(String password) {
         KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
         SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
         byte[] submittedPasswordHash = factory.generateSecret(spec).getEncoded();
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java
deleted file mode 100644
index 7e0cc79d4b46dc5011cbf5f295947d4e030f74b7..0000000000000000000000000000000000000000
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/UserDatabase.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package fr.univtln.bruno.samples.jaxrs.security;
-
-import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.security.Keys;
-import lombok.ToString;
-import lombok.extern.java.Log;
-
-import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.*;
-
-@Log
-@ToString
-public class UserDatabase {
-    public static final UserDatabase USER_DATABASE = new UserDatabase();
-
-    // We need a signing key for the id token, so we'll create one just for this example. Usually
-    // the key would be read from your application configuration instead.
-    public static final Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);
-
-    static {
-        try {
-            USER_DATABASE.addUser("John", "Doe", "john.doe@nowhere.com", "admin", EnumSet.of(Role.ADMIN));
-            USER_DATABASE.addUser("William", "Smith", "william.smith@here.net", "user", EnumSet.of(Role.USER));
-            USER_DATABASE.addUser("Mary", "Robert", "mary.roberts@here.net", "user", EnumSet.of(Role.USER));
-        } catch (InvalidKeySpecException e) {
-            e.printStackTrace();
-        } catch (NoSuchAlgorithmException e) {
-            e.printStackTrace();
-        }
-    }
-
-    private final Map<String, User> users = new HashMap<>();
-
-    public static void main(String[] args) {
-        USER_DATABASE.users.values().forEach(u->log.info(u.toString()));
-    }
-
-    public void addUser(String firstname, String lastname, String email, String password, EnumSet<Role> roles)
-            throws InvalidKeySpecException, NoSuchAlgorithmException {
-        users.put(email, User.builder().firstName(firstname).lastName(lastname).email(email).password(password).roles(roles).build());
-    }
-
-    public Map<String, User> getUsers() {
-        return Collections.unmodifiableMap(users);
-    }
-
-    public void removeUser(String email) {
-        users.remove(email);
-    }
-
-    public boolean checkPassword(String email, String password) throws InvalidKeySpecException, NoSuchAlgorithmException {
-        return users.get(email).checkPassword(password);
-    }
-
-    public User getUser(String email) {
-        return users.get(email);
-    }
-
-    public Set<Role> getUserRoles(String email) {
-        return users.get(email).getRoles();
-    }
-
-    public enum Role {ADMIN, USER, GUEST}
-}
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
similarity index 64%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
index 458387ed9684cd9a8e37c0ac84d345abae5dd93b..f09c007d5d6f86a0c9881997be131835f43f599c 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/BasicAuth.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/BasicAuth.java
@@ -1,4 +1,4 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.annotations;
 
 import jakarta.ws.rs.NameBinding;
 
@@ -9,6 +9,10 @@ import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+/**
+ * A annotation for method to be secured with Basic Auth
+ * @see fr.univtln.bruno.samples.jaxrs.security.filter.BasicAuthenticationFilter
+ */
 @NameBinding
 @Retention(RUNTIME)
 @Target({TYPE, METHOD})
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
similarity index 63%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
index 00b75227200f20567e1938404897ee454879f9e5..d7834fcb1b341ebe37d09d72a2f66cb5599ac77d 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/JWTAuth.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/annotations/JWTAuth.java
@@ -1,4 +1,4 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.annotations;
 
 import jakarta.ws.rs.NameBinding;
 
@@ -9,6 +9,10 @@ import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
+/**
+ * A annotation for method to be secured with Java Web Token (JWT)
+ * @see fr.univtln.bruno.samples.jaxrs.security.filter.JsonWebTokenFilter
+ */
 @NameBinding
 @Retention(RUNTIME)
 @Target({TYPE, METHOD})
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
similarity index 55%
rename from src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java
rename to src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
index 17d48f3bd0dddcbeb3cd7d9114e592bb56424267..a431005f235f14cd30c731e1281cb16ece95671d 100644
--- a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/AuthenticationFilter.java
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/BasicAuthenticationFilter.java
@@ -1,5 +1,8 @@
-package fr.univtln.bruno.samples.jaxrs.security;
+package fr.univtln.bruno.samples.jaxrs.security.filter;
 
+import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.BasicAuth;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import jakarta.annotation.Priority;
 import jakarta.annotation.security.DenyAll;
 import jakarta.annotation.security.PermitAll;
@@ -10,24 +13,24 @@ import jakarta.ws.rs.container.ContainerRequestFilter;
 import jakarta.ws.rs.container.ResourceInfo;
 import jakarta.ws.rs.core.Context;
 import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.SecurityContext;
 import jakarta.ws.rs.ext.Provider;
 import lombok.SneakyThrows;
 import lombok.extern.java.Log;
 
 import java.lang.reflect.Method;
-import java.security.Principal;
 import java.util.*;
 import java.util.stream.Collectors;
 
+/**
+ * Authentication filter is a JAX-RS filter (@Provider with implements ContainerRequestFilter) is applied to every request whose method is annotated with @BasicAuth
+ * as it is itself annotated with @BasicAuth (a personal annotation).
+ * It performs authentication and check permissions against the acceded method with a basic authentication.
+ */
 @BasicAuth
 @Provider
 @Priority(Priorities.AUTHENTICATION)
 @Log
-/**
- * This class if a filter for JAX-RS to perform authentication and to check permissions against the acceded method.
- */
-public class AuthenticationFilter implements ContainerRequestFilter {
+public class BasicAuthenticationFilter implements ContainerRequestFilter {
     private static final String AUTHORIZATION_PROPERTY = "Authorization";
     private static final String AUTHENTICATION_SCHEME = "Basic";
 
@@ -40,8 +43,8 @@ public class AuthenticationFilter implements ContainerRequestFilter {
     public void filter(ContainerRequestContext requestContext) {
         //We use reflection on the acceded method to look for security annotations.
         Method method = resourceInfo.getResourceMethod();
-        //if its PermitAll access is granted
-        //otherwise if its DenyAll the access is refused
+        //if it is PermitAll access is granted
+        //otherwise if it is DenyAll the access is refused
         if (!method.isAnnotationPresent(PermitAll.class)) {
             if (method.isAnnotationPresent(DenyAll.class)) {
                 requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
@@ -59,64 +62,40 @@ public class AuthenticationFilter implements ContainerRequestFilter {
                 return;
             }
 
-            //Get encoded username and password
+            //We extract the username and password encoded in base64
             final String encodedUserPassword = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
 
-            //Decode username and password
-            String usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword.getBytes()));
+            //We Decode username and password (username:password)
+            String[] usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword.getBytes())).split(":");
 
-            //Split username and password tokens
-            final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
-            final String username = tokenizer.nextToken();
-            final String password = tokenizer.nextToken();
+            final String username = usernameAndPassword[0];
+            final String password = usernameAndPassword[1];
 
-            log.info(username + " tries to log in with " + password);
+            log.info(username + " tries to log in");
 
-            //Verify user access
+            //We verify user access rights according to roles
+            //After Authentication we are doing Authorization
             if (method.isAnnotationPresent(RolesAllowed.class)) {
                 RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
-                EnumSet<UserDatabase.Role> rolesSet =
+                EnumSet<InMemoryLoginModule.Role> rolesSet =
                         Arrays.stream(rolesAnnotation.value())
-                                .map(r -> UserDatabase.Role.valueOf(r))
-                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(UserDatabase.Role.class)));
+                                .map(InMemoryLoginModule.Role::valueOf)
+                                .collect(Collectors.toCollection(() -> EnumSet.noneOf(InMemoryLoginModule.Role.class)));
 
                 //We check to login/password
-                if (!UserDatabase.USER_DATABASE.checkPassword(username, password)) {
+                if (!InMemoryLoginModule.USER_DATABASE.login(username, password)) {
                     requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                             .entity("Wrong username or password").build());
                     return;
                 }
                 //We check if the role is allowed
-                if (Collections.disjoint(rolesSet, UserDatabase.USER_DATABASE.getUserRoles(username))) {
+                if (!InMemoryLoginModule.isInRoles(rolesSet, username))
                     requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
                             .entity("Roles not allowed").build());
-                    return;
-                }
-
-                //We build a new securitycontext to transmit the security data to JAX-RS
-                requestContext.setSecurityContext(new SecurityContext() {
-
-                    @Override
-                    public Principal getUserPrincipal() {
-                        return UserDatabase.USER_DATABASE.getUser(username);
-                    }
-
-                    @Override
-                    public boolean isUserInRole(String role) {
-                        return UserDatabase.USER_DATABASE.getUserRoles(username).contains(UserDatabase.Role.valueOf(role));
-                    }
-
-                    @Override
-                    public boolean isSecure() {
-                        return true;
-                    }
-
-                    @Override
-                    public String getAuthenticationScheme() {
-                        return AUTHENTICATION_SCHEME;
-                    }
-                });
 
+                //We build a new SecurityContext Class to transmit the security data
+                // for this login attempt to JAX-RS
+                requestContext.setSecurityContext(MySecurityContext.newInstance(AUTHENTICATION_SCHEME, username));
 
             }
         }
diff --git a/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..088b2b26a2d42d0ee89d00b4802f2d059cf64d69
--- /dev/null
+++ b/src/main/java/fr/univtln/bruno/samples/jaxrs/security/filter/JsonWebTokenFilter.java
@@ -0,0 +1,110 @@
+package fr.univtln.bruno.samples.jaxrs.security.filter;
+
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
+import fr.univtln.bruno.samples.jaxrs.security.MySecurityContext;
+import fr.univtln.bruno.samples.jaxrs.security.annotations.JWTAuth;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import jakarta.annotation.Priority;
+import jakarta.annotation.security.DenyAll;
+import jakarta.annotation.security.PermitAll;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.ws.rs.Priorities;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.ext.Provider;
+import lombok.extern.java.Log;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.stream.Collectors;
+
+/**
+ * This class if a filter for JAX-RS to perform authentication via JWT.
+ */
+@JWTAuth
+@Provider
+@Priority(Priorities.AUTHENTICATION)
+@Log
+public class JsonWebTokenFilter implements ContainerRequestFilter {
+    private static final String AUTHORIZATION_PROPERTY = "Authorization";
+    private static final String AUTHENTICATION_SCHEME = "Bearer";
+
+    //We inject the data from the acceded resource.
+    @Context
+    private ResourceInfo resourceInfo;
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) {
+        //We use reflection on the acceded method to look for security annotations.
+        Method method = resourceInfo.getResourceMethod();
+
+        //if its PermitAll access is granted (without specific security context)
+        if (method.isAnnotationPresent(PermitAll.class)) return;
+
+        //otherwise if its DenyAll the access is refused
+        if (method.isAnnotationPresent(DenyAll.class)) {
+            requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
+                    .entity("Access denied to all users").build());
+            return;
+        }
+
+        //We get the authorization header from the request
+        final String authorization = requestContext.getHeaderString(AUTHORIZATION_PROPERTY);
+
+        //We check the credentials presence
+        if (authorization == null || authorization.isEmpty()) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Please provide your credentials").build());
+            return;
+        }
+
+        //We get the token
+        final String compactJwt = authorization.substring(AUTHENTICATION_SCHEME.length()).trim();
+        if (!authorization.contains(AUTHENTICATION_SCHEME) || compactJwt.isEmpty()) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Please provide correct credentials").build());
+            return;
+        }
+
+        String username = null;
+
+        //We check the validity of the token
+        try {
+            Jws<Claims> jws = Jwts.parserBuilder()
+                    .requireIssuer("sample-jaxrs")
+                    .setSigningKey(InMemoryLoginModule.KEY)
+                    .build()
+                    .parseClaimsJws(compactJwt);
+            username = jws.getBody().getSubject();
+
+            //We build a new securitycontext to transmit the security data to JAX-RS
+            requestContext.setSecurityContext(MySecurityContext.newInstance(AUTHENTICATION_SCHEME, username));
+        } catch (JwtException e) {
+            requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED)
+                    .entity("Wrong JWT token. " + e.getLocalizedMessage()).build());
+        }
+
+
+        //If present we extract the allowed roles annotation.
+        if (method.isAnnotationPresent(RolesAllowed.class)) {
+            RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
+            EnumSet<InMemoryLoginModule.Role> rolesSet =
+                    Arrays.stream(rolesAnnotation.value())
+                            .map(InMemoryLoginModule.Role::valueOf)
+                            .collect(Collectors.toCollection(() -> EnumSet.noneOf(InMemoryLoginModule.Role.class)));
+
+            //We check if the role is allowed
+            if (!InMemoryLoginModule.isInRoles(rolesSet, username))
+                requestContext.abortWith(Response.status(Response.Status.FORBIDDEN)
+                        .entity("Roles not allowed").build());
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java b/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
index 2a67e62898831ed24fa7e4ed332214022f9bb20a..aca5882296cd7449729e65014214faaa4c8492e8 100644
--- a/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
+++ b/src/test/java/fr/univtln/bruno/samples/jaxrs/ServerIT.java
@@ -1,7 +1,13 @@
 package fr.univtln.bruno.samples.jaxrs;
 
 import fr.univtln.bruno.samples.jaxrs.model.BiblioModel.Auteur;
+import fr.univtln.bruno.samples.jaxrs.security.InMemoryLoginModule;
 import fr.univtln.bruno.samples.jaxrs.server.BiblioServer;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
 import jakarta.ws.rs.client.Client;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.Entity;
@@ -13,7 +19,11 @@ import org.glassfish.grizzly.http.server.HttpServer;
 import org.glassfish.jersey.message.internal.MediaTypes;
 import org.junit.*;
 
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 
 import static org.junit.Assert.*;
@@ -205,4 +215,72 @@ public class ServerIT {
         assertEquals(1, auteurs.size());
         assertEquals("Marie", auteurs.get(0).getPrenom());
     }
+
+    @Test
+    public void refusedLogin() {
+        Response result = webTarget.path("biblio/login")
+                .request()
+                .get();
+        assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), result.getStatus());
+    }
+
+    @Test
+    public void acceptedLogin() {
+        String email="john.doe@nowhere.com";
+        String password="admin";
+        Response result = webTarget.path("biblio/login")
+                .request()
+                .accept(MediaType.TEXT_PLAIN)
+                .header("Authorization",  "Basic "+java.util.Base64.getEncoder().encodeToString((email+":"+password).getBytes()))
+                .get();
+
+        String entity = result.readEntity(String.class);
+        assertEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+        Jws<Claims> jws = Jwts.parserBuilder()
+                .setSigningKey(InMemoryLoginModule.KEY)
+                .build()
+                .parseClaimsJws(entity);
+        assertEquals(email,jws.getBody().getSubject());
+    }
+
+    @Test
+    public void jwtAccess() {
+        //Log in to get the token
+        String email="john.doe@nowhere.com";
+        String password="admin";
+        String token = webTarget.path("biblio/login")
+                .request()
+                .accept(MediaType.TEXT_PLAIN)
+                .header("Authorization",  "Basic "+java.util.Base64.getEncoder().encodeToString((email+":"+password).getBytes()))
+                .get(String.class);
+
+        //We access a JWT protected URL with the token
+        Response result = webTarget.path("biblio/secured")
+                .request()
+                .header( "Authorization",  "Bearer "+token)
+                .get();
+        assertEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+        assertEquals("Access with JWT ok for Doe, John <john.doe@nowhere.com>",result.readEntity(String.class));
+    }
+
+    @Test
+    public void jwtAccessDenied() {
+        String forgedToken = Jwts.builder()
+                .setIssuer("sample-jaxrs")
+                .setIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
+                .setSubject("john.doe@nowhere.com")
+                .claim("firstname", "John")
+                .claim("lastname", "Doe")
+                .setExpiration(Date.from(LocalDateTime.now().plus(15, ChronoUnit.MINUTES).atZone(ZoneId.systemDefault()).toInstant()))
+                //A RANDOM KEY DIFFERENT FROM THE SERVER
+                .signWith( Keys.secretKeyFor(SignatureAlgorithm.HS256)).compact();
+
+        //We access a JWT protected URL with the token
+        Response result = webTarget.path("biblio/secured")
+                .request()
+                .header( "Authorization",  "Bearer "+forgedToken)
+                .get();
+        assertNotEquals(Response.Status.OK.getStatusCode(), result.getStatus());
+    }
+
 }
diff --git a/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java b/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..49eb303cf0ebb96a3d68b0f35654d652f4763454
--- /dev/null
+++ b/src/test/java/fr/univtln/bruno/samples/jaxrs/security/UserTest.java
@@ -0,0 +1,27 @@
+
+package fr.univtln.bruno.samples.jaxrs.security;
+
+import org.junit.Test;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.EnumSet;
+
+import static org.junit.Assert.assertTrue;
+
+public class UserTest {
+
+    @Test
+    public void testBuilder() throws InvalidKeySpecException, NoSuchAlgorithmException {
+        String lastname="Doe", firstname="John", email="j.d@here.com", password="mypass";
+            User user  = User.builder()
+                    .lastName(lastname)
+                    .firstName(firstname)
+                    .email(email)
+                    .password(password)
+                    .roles(EnumSet.of(InMemoryLoginModule.Role.ADMIN))
+                    .build();
+            assertTrue(user.checkPassword(password));
+            assertTrue(user.contains(InMemoryLoginModule.Role.valueOf("ADMIN")));
+    }
+}
\ No newline at end of file