Adding refresh token for authentication
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
index fc0de01..e5c2553 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
@@ -61,6 +61,22 @@
         this.nonce = nonce;
     }
 
+    /**
+     * Creates a new token info instance for the given user.
+     * The lifetime in milliseconds defines the invalidation date by
+     * adding the lifetime to the current time of instantiation.
+     *
+     * @param user The user name
+     * @param lifetime The number of milliseconds after that the token is invalid
+     * @param nonce Should be a random number and different for each instance.
+     */
+    public SimpleTokenData(final String user, final Duration lifetime, final long nonce) {
+        this.user=user;
+        this.created = Instant.now( );
+        this.validBefore = created.plus( lifetime );
+        this.nonce = nonce;
+    }
+
     @Override
     public final String getUser() {
         return user;
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
index c96c4e2..e4ab166 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/StringToken.java
@@ -26,23 +26,34 @@
 public class StringToken implements Token
 {
     final TokenData metadata;
-    final String token;
+    final String data;
+    final String id;
+    final TokenType type;
 
-    public StringToken(String tokenData, TokenData metadata) {
-        this.token = tokenData;
+    public StringToken(String id, String tokenData, TokenData metadata) {
+        this.id = id;
+        this.data = tokenData;
         this.metadata = metadata;
+        this.type = TokenType.ACCESS_TOKEN;
+    }
+
+    public StringToken(TokenType type, String id, String tokenData, TokenData metadata) {
+        this.id = id;
+        this.data = tokenData;
+        this.metadata = metadata;
+        this.type = type;
     }
 
     @Override
     public String getData( )
     {
-        return token;
+        return data;
     }
 
     @Override
     public byte[] getBytes( )
     {
-        return token.getBytes( );
+        return data.getBytes( );
     }
 
     @Override
@@ -50,4 +61,16 @@
     {
         return metadata;
     }
+
+    @Override
+    public String getId( )
+    {
+        return id;
+    }
+
+    @Override
+    public TokenType getType( )
+    {
+        return type;
+    }
 }
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
index 221a57b..56b6666 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/Token.java
@@ -26,9 +26,34 @@
 public interface Token
 {
 
+    /**
+     * The token id, if it exists, otherwise a empty string.
+     * @return
+     */
+    String getId();
+
+    /**
+     * Returns the token type (access or refresh token)
+     * @return the token type
+     */
+    TokenType getType();
+
+    /**
+     * The string representation of the token data. It depends on the token algorithm,
+     * what kind of string conversion is used (e.g. Base64)
+     * @return the token string
+     */
     String getData();
 
+    /**
+     * The token as byte array
+     * @return
+     */
     byte[] getBytes();
 
+    /**
+     * The token meta data, like expiration time.
+     * @return the metadata
+     */
     TokenData getMetadata();
 }
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java
new file mode 100644
index 0000000..9441d08
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenType.java
@@ -0,0 +1,48 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public enum TokenType
+{
+    REFRESH_TOKEN("refresh_token"), ACCESS_TOKEN( "access_token" ), ALL( "*" ),;
+
+    private String claim;
+
+    TokenType( String claim )
+    {
+        this.claim = claim;
+    }
+
+    public String getClaim() {
+        return this.claim;
+    }
+
+    public static TokenType ofClaim(String claim) {
+        TokenType[] vals = values( );
+        for (int i=0; i< vals.length; i++) {
+            if (vals[i].getClaim().equals(claim)) {
+                return vals[i];
+            }
+        }
+        return null;
+    }
+}
diff --git a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
index b67a134..de34f94 100644
--- a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
+++ b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/main/java/org/apache/archiva/redback/authentication/jwt/JwtAuthenticator.java
@@ -21,12 +21,14 @@
 
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.IncorrectClaimException;
 import io.jsonwebtoken.Jws;
 import io.jsonwebtoken.JwsHeader;
 import io.jsonwebtoken.JwtException;
 import io.jsonwebtoken.JwtParser;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.MissingClaimException;
 import io.jsonwebtoken.SignatureAlgorithm;
 import io.jsonwebtoken.SigningKeyResolverAdapter;
 import io.jsonwebtoken.UnsupportedJwtException;
@@ -44,6 +46,7 @@
 import org.apache.archiva.redback.authentication.Token;
 import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
 import org.apache.archiva.redback.authentication.TokenData;
+import org.apache.archiva.redback.authentication.TokenType;
 import org.apache.archiva.redback.configuration.UserConfiguration;
 import org.apache.archiva.redback.configuration.UserConfigurationKeys;
 import org.apache.commons.lang3.StringUtils;
@@ -78,9 +81,11 @@
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Properties;
+import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -140,7 +145,10 @@
 {
     private static final Logger log = LoggerFactory.getLogger( JwtAuthenticator.class );
 
+    // 4 hours for standard tokens
     public static final String DEFAULT_LIFETIME = "14400000";
+    // 7 days for refresh tokens
+    public static final String DEFAULT_REFRESH_LIFETIME = "604800000";
     public static final String DEFAULT_KEYFILE = "jwt-key.xml";
     public static final String ID = "JwtAuthenticator";
     public static final String PROP_PRIV_ALG = "privateAlgorithm";
@@ -151,6 +159,7 @@
     public static final String PROP_PUBLICKEY = "publicKey";
     public static final String PROP_KEYID = "keyId";
     private static final String ISSUER = "archiva.apache.org/redback";
+    private static final String TOKEN_TYPE = "token_type";
 
 
     @Inject
@@ -168,9 +177,14 @@
     AtomicLong keyCounter;
     final SigningKeyResolver resolver = new SigningKeyResolver( );
     final ReadWriteLock lock = new ReentrantReadWriteLock( );
-    private JwtParser parser;
-    private Duration lifetime;
+    private Duration tokenLifetime;
+    private Duration refreshTokenLifetime;
+    private Map<TokenType, JwtParser> parserMap = new HashMap<>( );
 
+
+    private JwtParser getParser(TokenType type) {
+        return parserMap.get( type );
+    }
     public class SigningKeyResolver extends SigningKeyResolverAdapter
     {
 
@@ -242,12 +256,24 @@
             // In memory key store is the default
             addNewKey( );
         }
-        this.parser = Jwts.parserBuilder( )
+        this.parserMap.put(TokenType.ALL,  Jwts.parserBuilder( )
             .setSigningKeyResolver( getResolver( ) )
             .requireIssuer( ISSUER )
-            .build( );
+            .build( ));
+        this.parserMap.put(TokenType.ACCESS_TOKEN,  Jwts.parserBuilder( )
+            .setSigningKeyResolver( getResolver( ) )
+            .requireIssuer( ISSUER )
+            .require( TOKEN_TYPE, TokenType.ACCESS_TOKEN.getClaim() )
+            .build( ));
+        this.parserMap.put(TokenType.REFRESH_TOKEN,  Jwts.parserBuilder( )
+            .setSigningKeyResolver( getResolver( ) )
+            .requireIssuer( ISSUER )
+            .require( TOKEN_TYPE, TokenType.REFRESH_TOKEN.getClaim() )
+            .build( ));
 
-        lifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_LIFETIME_MS, DEFAULT_LIFETIME ) ) );
+
+        tokenLifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_LIFETIME_MS, DEFAULT_LIFETIME ) ) );
+        refreshTokenLifetime = Duration.ofMillis( Long.parseLong( userConfiguration.getString( AUTHENTICATION_JWT_REFRESH_LIFETIME_MS, DEFAULT_REFRESH_LIFETIME ) ) );
     }
 
     private void addNewSecretKey( Long id, SecretKey key )
@@ -661,33 +687,94 @@
     {
         final KeyHolder signerKey = getSignerKey( );
         Instant now = Instant.now( );
-        Instant expiration = now.plus( lifetime );
+        Instant expiration = now.plus( tokenLifetime );
         final String token = Jwts.builder( )
             .setSubject( userId )
             .setIssuer( ISSUER )
+            .claim( TOKEN_TYPE, TokenType.ACCESS_TOKEN.getClaim( ) )
             .setIssuedAt( Date.from( now ) )
             .setExpiration( Date.from( expiration ) )
             .setHeaderParam( JwsHeader.KEY_ID, signerKey.getId( ).toString( ) )
             .signWith( signerKey.getSignerKey( ) ).compact( );
-        TokenData metadata = new SimpleTokenData( userId, lifetime.toMillis( ), 0 );
-        return new StringToken( token, metadata );
+        TokenData metadata = new SimpleTokenData( userId, tokenLifetime, 0 );
+        return new StringToken("", token, metadata );
+    }
+
+    /**
+     * Creates a token for the given user id. The token contains the following data:
+     * <ul>
+     *     <li>the userid as subject</li>
+     *     <li>a issuer archiva.apache.org/redback</li>
+     *     <li>a id header with the key id</li>
+     * </ul>the user id as subject.
+     *
+     * @param userId the user identifier to set as subject
+     * @param type the token type that indicates if this token is a access or refresh token
+     * @return the token string
+     */
+    public Token generateToken( String userId, TokenType type )
+    {
+        if (type==TokenType.ACCESS_TOKEN) {
+            return generateToken( userId );
+        } else if (type == TokenType.REFRESH_TOKEN)
+        {
+            return generateRefreshToken( userId );
+        } else {
+            throw new RuntimeException( "Invalid token type requested" );
+        }
+    }
+
+    private Token generateRefreshToken(String userId) {
+        final KeyHolder signerKey = getSignerKey( );
+        Instant now = Instant.now( );
+        Instant expiration = now.plus( refreshTokenLifetime );
+        final String id = UUID.randomUUID( ).toString( );
+        final String token = Jwts.builder( )
+            .setSubject( userId )
+            .setIssuer( ISSUER )
+            .setIssuedAt( Date.from( now ) )
+            .setId( id )
+            .claim( TOKEN_TYPE, TokenType.REFRESH_TOKEN.getClaim() )
+            .setExpiration( Date.from( expiration ) )
+            .setHeaderParam( JwsHeader.KEY_ID, signerKey.getId( ).toString( ) )
+            .signWith( signerKey.getSignerKey( ) ).compact( );
+        TokenData metadata = new SimpleTokenData( userId, refreshTokenLifetime, 0 );
+        return new StringToken( TokenType.REFRESH_TOKEN, id, token, metadata );
+    }
+
+    /**
+     * Returns a token object from the given token String
+     *
+     * @param tokenData the string representation of the token
+     * @return the token instance
+     */
+    public Token tokenFromString(String tokenData) {
+        Jws<Claims> parsedToken = parseToken( tokenData );
+        String userId = parsedToken.getBody( ).getSubject( );
+        TokenType type = TokenType.ofClaim( parsedToken.getBody( ).get( TOKEN_TYPE, String.class ) );
+        String id = parsedToken.getBody( ).getId( );
+        Instant expiration = parsedToken.getBody( ).getExpiration( ).toInstant( );
+        Instant issuedAt = parsedToken.getBody( ).getIssuedAt( ).toInstant( );
+        long lifetime = Duration.between( issuedAt, expiration ).toMillis( );
+        TokenData metadata = new SimpleTokenData( userId, lifetime, 0 );
+        return new StringToken( type, id, tokenData, metadata );
     }
 
     /**
      * Allows to renew a token based on the origin token. If the presented <code>origin</code>
      * is valid, a new token with refreshed expiration time will be returned.
      *
-     * @param origin the origin token
+     * @param refreshToken the refresh token
      * @return the newly created token
      * @throws AuthenticationException if the given origin token is not valid
      */
-    public Token renewToken(String origin) throws AuthenticationException {
+    public Token refreshAccessToken( String refreshToken) throws TokenAuthenticationException {
         try
         {
-            Jws<Claims> signature = this.parser.parseClaimsJws( origin );
-            return generateToken( signature.getBody( ).getSubject( ) );
-        } catch (JwtException e) {
-            throw new AuthenticationException( "Could not renew the token " + e.getMessage( ) );
+            String subject = verify( refreshToken, TokenType.REFRESH_TOKEN );
+            return generateToken( subject );
+        } catch ( JwtException e) {
+            throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "unknown error " + e.getMessage( ) );
         }
     }
 
@@ -699,21 +786,26 @@
      * @throws JwtException if the token data is not valid anymore
      */
     public Jws<Claims> parseToken( String token) throws JwtException {
-        return parser.parseClaimsJws( token );
+        return getParser(TokenType.ALL).parseClaimsJws( token );
     }
 
     /**
      * Verifies the given JWT Token and returns the stored subject, if successful
-     * If the verification failed a AuthenticationException is thrown.
+     * If the verification failed a TokenAuthenticationException is thrown.
      * @param token the JWT representation
      * @return the subject of the JWT
-     * @throws AuthenticationException if the verification failed
+     * @throws TokenAuthenticationException if the verification failed
      */
     public String verify( String token ) throws TokenAuthenticationException
     {
+        return verify( token, TokenType.ACCESS_TOKEN );
+    }
+
+    public String verify( String token, TokenType type ) throws TokenAuthenticationException
+    {
         try
         {
-            Jws<Claims> signature = this.parser.parseClaimsJws( token );
+            Jws<Claims> signature = getParser(type).parseClaimsJws( token );
             String subject = signature.getBody( ).getSubject( );
             if ( StringUtils.isEmpty( subject ) )
             {
@@ -738,6 +830,9 @@
         catch (JwtKeyIdNotFoundException e) {
             throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "signer key does not exist" );
         }
+        catch ( MissingClaimException |IncorrectClaimException e ) {
+            throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "the token type is not correct - expected claim "+type.getClaim() );
+        }
         catch ( JwtException e) {
             log.debug( "Unknown JwtException {}, {}", e.getClass( ), e.getMessage( ) );
             throw new TokenAuthenticationException( BearerError.INVALID_TOKEN, "unknown error " + e.getMessage( ) );
@@ -836,7 +931,7 @@
      * @return the lifetime as duration
      */
     public Duration getTokenLifetime() {
-        return this.lifetime;
+        return this.tokenLifetime;
     }
 
     /**
@@ -844,7 +939,7 @@
      * @param lifetime the lifetime as duration
      */
     public void setTokenLifetime(Duration lifetime) {
-        this.lifetime = lifetime;
+        this.tokenLifetime = lifetime;
     }
 
     public UserConfiguration getUserConfiguration( )
diff --git a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
index 6acf4a0..3ff7128 100644
--- a/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
+++ b/redback-authentication/redback-authentication-providers/redback-authentication-jwt/src/test/java/org/apache/archiva/redback/authentication/jwt/AbstractJwtTest.java
@@ -29,6 +29,7 @@
 import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
 import org.apache.archiva.redback.authentication.Token;
 import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
+import org.apache.archiva.redback.authentication.TokenType;
 import org.apache.archiva.redback.configuration.DefaultUserConfiguration;
 import org.apache.archiva.redback.configuration.UserConfigurationException;
 import org.apache.commons.configuration2.BaseConfiguration;
@@ -39,6 +40,7 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Map;
+import java.util.UUID;
 
 import static org.junit.jupiter.api.Assertions.*;
 
@@ -119,12 +121,6 @@
         assertTrue( Instant.now( ).isBefore( token.getMetadata( ).validBefore( ) ) );
     }
 
-
-    @Test
-    void authenticate( )
-    {
-    }
-
     @Test
     void renewSigningKey( )
     {
@@ -231,5 +227,32 @@
         assertFalse( result.isAuthenticated( ) );
     }
 
+    @Test
+    void refreshToken() throws TokenAuthenticationException
+    {
+        Token token = jwtAuthenticator.generateToken( "bilbo_baggins" , TokenType.REFRESH_TOKEN);
+        assertNotNull( token );
+        assertTrue( token.getType( ).equals( TokenType.REFRESH_TOKEN ) );
+        UUID tokenId = UUID.fromString( token.getId( ) );
+        assertNotNull( tokenId );
+        Token accessToken = jwtAuthenticator.refreshAccessToken( token.getData() );
+        assertNotNull( accessToken );
+        assertTrue( accessToken.getType( ).equals( TokenType.ACCESS_TOKEN ) );
+
+    }
+
+    @Test
+    void invalidRefreshWithAccessToken() throws TokenAuthenticationException
+    {
+        Token token = jwtAuthenticator.generateToken( "bilbo_baggins");
+        assertNotNull( token );
+        assertTrue( token.getType( ).equals( TokenType.ACCESS_TOKEN ) );
+        TokenAuthenticationException thrownException = assertThrows( TokenAuthenticationException.class, ( ) -> {
+            jwtAuthenticator.refreshAccessToken( token.getData( ) );
+        } );
+        assertEquals( BearerError.INVALID_TOKEN, thrownException.getError( ) );
+        assertTrue( thrownException.getMessage( ).contains( "the token type is not correct - expected claim " + TokenType.REFRESH_TOKEN.getClaim( ) ) );
+    }
+
 
 }
diff --git a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
index a54ab4d..f1c6e87 100644
--- a/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
+++ b/redback-configuration/src/main/java/org/apache/archiva/redback/configuration/UserConfigurationKeys.java
@@ -187,25 +187,25 @@
     String MAIL_DEFAULT_LOCALE = "mail.locale";
 
     /**
-     * Defines, where the key for JWT encryption / decryption is stored.
+     * The property for defining, where the key for JWT encryption / decryption is stored.
      * Currently only memory and plainfile are supported
      * {@value}
      */
     String AUTHENTICATION_JWT_KEYSTORETYPE = "authentication.jwt.keystoreType";
     /**
-     * Keystore type name for memory keystore: {@value}
+     * The property value for memory keystore: {@value}
      */
     String AUTHENTICATION_JWT_KEYSTORETYPE_MEMORY = "memory";
     /**
-     * Keystore type name for plain file keystore: {@value}
+     * The property value for plain file keystore: {@value}
      */
     String AUTHENTICATION_JWT_KEYSTORETYPE_PLAINFILE = "plainfile";
     /**
-     * Defines the used signature algorithm for JWT signing: {@value}
+     * The property for defining the used signature algorithm for JWT signing: {@value}
      */
     String AUTHENTICATION_JWT_SIGALG = "authentication.jwt.signatureAlgorithm";
     /**
-     * Defines the maximum number of keys to keep in memory for verificatio: {@value}
+     * The property for defining the maximum number of keys to keep in memory for verification: {@value}
      */
     String AUTHENTICATION_JWT_MAX_KEYS = "authentication.jwt.maxInMemoryKeys";
 
@@ -260,13 +260,18 @@
 
 
     /**
-     * Path to the file where the JWT key is stored: {@value}
+     * The property for the path to the file where the JWT key is stored: {@value}
      */
     String AUTHENTICATION_JWT_KEYFILE = "authentication.jwt.keyfile";
 
     /**
-     * The lifetime in ms of the generated tokens: {@value}
+     * The property for lifetime in ms of the generated tokens: {@value}
      */
     String AUTHENTICATION_JWT_LIFETIME_MS = "authentication.jwt.lifetimeMs";
 
+    /**
+     * The property for lifetime in ms of the generated refresh tokens: {@value}
+     */
+    String AUTHENTICATION_JWT_REFRESH_LIFETIME_MS = "authentication.jwt.refreshLifetimeMs";
+
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
index 34bde92..c852735 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/PingResult.java
@@ -19,6 +19,7 @@
  */
 
 import javax.xml.bind.annotation.XmlRootElement;
+import java.time.OffsetDateTime;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -27,13 +28,15 @@
 public class PingResult
 {
     boolean success;
+    OffsetDateTime requestTime;
 
     public PingResult() {
-
+        this.requestTime = OffsetDateTime.now( );
     }
 
     public PingResult( boolean success ) {
         this.success = success;
+        this.requestTime = OffsetDateTime.now( );
     }
 
     public boolean isSuccess( )
@@ -45,4 +48,14 @@
     {
         this.success = success;
     }
+
+    public OffsetDateTime getRequestTime( )
+    {
+        return requestTime;
+    }
+
+    public void setRequestTime( OffsetDateTime requestTime )
+    {
+        this.requestTime = requestTime;
+    }
 }
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java
new file mode 100644
index 0000000..a0302c8
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RefreshTokenRequest.java
@@ -0,0 +1,77 @@
+package org.apache.archiva.redback.rest.api.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="refreshToken")
+public class RefreshTokenRequest
+{
+    String grantType;
+    String refreshToken;
+    String scope;
+
+    public RefreshTokenRequest( )
+    {
+    }
+
+    public RefreshTokenRequest( String grantType, String refreshToken, String scope )
+    {
+        this.grantType = grantType;
+        this.refreshToken = refreshToken;
+        this.scope = scope;
+    }
+
+    @XmlElement(name = "grant_type")
+    public String getGrantType( )
+    {
+        return grantType;
+    }
+
+    public void setGrantType( String grantType )
+    {
+        this.grantType = grantType;
+    }
+
+    @XmlElement(name="refresh_token")
+    public String getRefreshToken( )
+    {
+        return refreshToken;
+    }
+
+    public void setRefreshToken( String refreshToken )
+    {
+        this.refreshToken = refreshToken;
+    }
+
+    @XmlElement(name="scope")
+    public String getScope( )
+    {
+        return scope;
+    }
+
+    public void setScope( String scope )
+    {
+        this.scope = scope;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java
new file mode 100644
index 0000000..470344d
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/RequestTokenRequest.java
@@ -0,0 +1,156 @@
+package org.apache.archiva.redback.rest.api.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="refreshToken")
+public class RequestTokenRequest
+{
+    String grantType = "authorization_code";
+    String clientId;
+    String clientSecret;
+    String code;
+    String scope = "";
+    String state  = "";
+    String userId;
+    String password;
+    String redirectUri;
+
+    public RequestTokenRequest() {
+
+    }
+
+    public RequestTokenRequest( String userId, String password )
+    {
+        this.userId = userId;
+        this.password = password;
+    }
+
+    public RequestTokenRequest( String userId, String password, String scope )
+    {
+        this.userId = userId;
+        this.password = password;
+        this.scope = scope;
+    }
+
+    @XmlElement(name = "grant_type", required = true, nillable = false)
+    public String getGrantType( )
+    {
+        return grantType;
+    }
+
+    public void setGrantType( String grantType )
+    {
+        this.grantType = grantType;
+    }
+
+    @XmlElement(name="client_id", required = false, nillable = true)
+    public String getClientId( )
+    {
+        return clientId;
+    }
+
+    public void setClientId( String clientId )
+    {
+        this.clientId = clientId;
+    }
+
+    @XmlElement(name="client_secret", required = false, nillable = true)
+    public String getClientSecret( )
+    {
+        return clientSecret;
+    }
+
+    public void setClientSecret( String clientSecret )
+    {
+        this.clientSecret = clientSecret;
+    }
+
+    @XmlElement(name="scope", required = false, nillable = true)
+    public String getScope( )
+    {
+        return scope;
+    }
+
+    public void setScope( String scope )
+    {
+        this.scope = scope;
+    }
+
+    @XmlElement(name="user_id", required = true, nillable = false)
+    public String getUserId( )
+    {
+        return userId;
+    }
+
+    @XmlElement(name="user_id", required = true, nillable = false)
+    public void setUserId( String userId )
+    {
+        this.userId = userId;
+    }
+
+    @XmlElement(name="password", required = true, nillable = false)
+    public String getPassword( )
+    {
+        return password;
+    }
+
+    public void setPassword( String password )
+    {
+        this.password = password;
+    }
+
+    @XmlElement(name="code", required = false, nillable = false)
+    public String getCode( )
+    {
+        return code;
+    }
+
+    public void setCode( String code )
+    {
+        this.code = code;
+    }
+
+    @XmlElement(name="redirect_uri", required = false, nillable = false)
+    public String getRedirectUri( )
+    {
+        return redirectUri;
+    }
+
+    public void setRedirectUri( String redirectUri )
+    {
+        this.redirectUri = redirectUri;
+    }
+
+    @XmlElement(name="state", required = false, nillable = false)
+    public String getState( )
+    {
+        return state;
+    }
+
+    public void setState( String state )
+    {
+        this.state = state;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java
new file mode 100644
index 0000000..6c9d427
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/TokenResponse.java
@@ -0,0 +1,146 @@
+package org.apache.archiva.redback.rest.api.model;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.redback.authentication.Token;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name="token")
+public class TokenResponse
+{
+    String accessToken;
+    String tokenType = "bearer";
+    long expiresIn;
+    String refreshToken;
+    String scope;
+    String state;
+
+    public TokenResponse( )
+    {
+    }
+
+    public TokenResponse( String accessToken, String tokenType, long expiresIn, String refreshToken, String scope )
+    {
+        this.accessToken = accessToken;
+        this.tokenType = tokenType;
+        this.expiresIn = expiresIn;
+        this.refreshToken = refreshToken;
+        this.scope = scope;
+    }
+
+    public TokenResponse( String accessToken, long expiresIn, String refreshToken, String scope )
+    {
+        this.accessToken = accessToken;
+        this.expiresIn = expiresIn;
+        this.refreshToken = refreshToken;
+        this.scope = scope;
+    }
+
+    public TokenResponse( Token accessToken, Token refreshToken )
+    {
+        this.expiresIn = Duration.between( Instant.now( ), accessToken.getMetadata( ).validBefore( ) ).getSeconds();
+        this.accessToken = accessToken.getData( );
+        this.refreshToken = refreshToken.getData( );
+        this.scope = "";
+    }
+
+    public TokenResponse( Token accessToken, Token refreshToken , String scope, String state)
+    {
+        this.expiresIn = Duration.between( Instant.now( ), accessToken.getMetadata( ).validBefore( ) ).getSeconds();
+        this.accessToken = accessToken.getData( );
+        this.refreshToken = refreshToken.getData( );
+        this.scope = scope;
+        this.state = state;
+    }
+
+    @XmlElement(name="access_token")
+    public String getAccessToken( )
+    {
+        return accessToken;
+    }
+
+    public void setAccessToken( String accessToken )
+    {
+        this.accessToken = accessToken;
+    }
+
+    @XmlElement(name="token_type")
+    public String getTokenType( )
+    {
+        return tokenType;
+    }
+
+    public void setTokenType( String tokenType )
+    {
+        this.tokenType = tokenType;
+    }
+
+    @XmlElement(name="expires_in")
+    public long getExpiresIn( )
+    {
+        return expiresIn;
+    }
+
+    public void setExpiresIn( long expiresIn )
+    {
+        this.expiresIn = expiresIn;
+    }
+
+    @XmlElement(name="refresh_token")
+    public String getRefreshToken( )
+    {
+        return refreshToken;
+    }
+
+    public void setRefreshToken( String refreshToken )
+    {
+        this.refreshToken = refreshToken;
+    }
+
+    public String getScope( )
+    {
+        return scope;
+    }
+
+    public void setScope( String scope )
+    {
+        this.scope = scope;
+    }
+
+    public String getState( )
+    {
+        return state;
+    }
+
+    public void setState( String state )
+    {
+        this.state = state;
+    }
+
+    public boolean hasState() {
+        return state != null && state.length( ) > 0;
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
index 6f8b448..6ecf666 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/LoginService.java
@@ -56,7 +56,7 @@
     @GET
     @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
     @RedbackAuthorization( noRestriction = true )
-    PingResult ping()
+    Boolean ping()
         throws RedbackServiceException;
 
 
@@ -65,7 +65,7 @@
     @GET
     @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
     @RedbackAuthorization( noRestriction = false, noPermission = true )
-    PingResult pingWithAutz()
+    Boolean pingWithAutz()
         throws RedbackServiceException;
 
     /**
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
index 0363c85..1fbc633 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/UserService.java
@@ -151,7 +151,7 @@
     @GET
     @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } )
     @RedbackAuthorization( noRestriction = true )
-    PingResult ping()
+    Boolean ping()
         throws RedbackServiceException;
 
     @Path( "removeFromCache/{userName}" )
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
index 88c8f4d..cf75395 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/AuthenticationService.java
@@ -25,7 +25,10 @@
 import org.apache.archiva.redback.rest.api.model.ActionStatus;
 import org.apache.archiva.redback.rest.api.model.LoginRequest;
 import org.apache.archiva.redback.rest.api.model.PingResult;
+import org.apache.archiva.redback.rest.api.model.RefreshTokenRequest;
+import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
 import org.apache.archiva.redback.rest.api.model.Token;
+import org.apache.archiva.redback.rest.api.model.TokenResponse;
 import org.apache.archiva.redback.rest.api.model.User;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
 
@@ -43,17 +46,6 @@
 public interface AuthenticationService
 {
 
-    @Path( "requestkey" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON } )
-    @RedbackAuthorization( noRestriction = true )
-    Token requestOnetimeToken( @QueryParam( "providerKey" ) String providedKey,
-                        @QueryParam( "principal" ) String principal,
-                        @QueryParam( "purpose" ) String purpose,
-                        @QueryParam( "expirationSeconds" ) int expirationSeconds )
-        throws RedbackServiceException;
-
-
     @Path( "ping" )
     @GET
     @Produces( { MediaType.APPLICATION_JSON } )
@@ -74,7 +66,7 @@
      * The bearer token can be added to the HTTP header on further requests to authenticate.
      *
      */
-    @Path( "authenticate" )
+    @Path( "token" )
     @POST
     @RedbackAuthorization( noRestriction = true, noPermission = true )
     @Produces( { MediaType.APPLICATION_JSON } )
@@ -83,23 +75,23 @@
             @ApiResponse( description = "The bearer token. The token data contains the token string that should be added to the Bearer header" )
         }
     )
-    Token logIn( LoginRequest loginRequest )
+    TokenResponse logIn( RequestTokenRequest loginRequest )
         throws RedbackServiceException;
 
     /**
      * Renew the bearer token. The request must send a bearer token in the HTTP header
      *
      */
-    @Path( "authenticate" )
-    @GET
+    @Path( "refresh" )
+    @POST
     @RedbackAuthorization( noRestriction = false, noPermission = true )
     @Produces( { MediaType.APPLICATION_JSON } )
-    @Operation( summary = "Creates a new bearer token. The requestor must present a still valid bearer token in the HTTP header.",
+    @Operation( summary = "Creates a new bearer token. The requester must present a still valid bearer token in the HTTP header.",
         responses = {
             @ApiResponse( description = "The new bearer token," )
         }
     )
-    Token renewToken( )
+    TokenResponse refreshToken( RefreshTokenRequest refreshTokenRequest )
         throws RedbackServiceException;
 
 
@@ -107,21 +99,11 @@
      * simply check if current user has an http session opened with authz passed and return user data
      * @since 1.4
      */
-    @Path( "isAuthenticated" )
+    @Path( "authenticated" )
     @GET
     @Produces( { MediaType.APPLICATION_JSON } )
     @RedbackAuthorization( noRestriction = true )
-    User isLogged()
+    User getAuthenticatedUser()
         throws RedbackServiceException;
 
-    /**
-     * clear user http session
-     * @since 1.4
-     */
-    @Path( "logout" )
-    @GET
-    @Produces( { MediaType.APPLICATION_JSON } )
-    @RedbackAuthorization( noRestriction = true, noPermission = true )
-    ActionStatus logout()
-        throws RedbackServiceException;
 }
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
index a3f5055..d51d3a3 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
@@ -126,16 +126,16 @@
         return key.getKey( );
     }
 
-    public PingResult ping()
+    public Boolean ping()
         throws RedbackServiceException
     {
-        return new PingResult( true);
+        return Boolean.TRUE;
     }
 
-    public PingResult pingWithAutz()
+    public Boolean pingWithAutz()
         throws RedbackServiceException
     {
-        return new PingResult( true );
+        return Boolean.TRUE;
     }
 
     public User logIn( LoginRequest loginRequest )
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
index 320dc8f..08ea8af 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultUserService.java
@@ -490,10 +490,10 @@
     }
 
     @Override
-    public PingResult ping()
+    public Boolean ping()
         throws RedbackServiceException
     {
-        return new PingResult( true );
+        return Boolean.TRUE;
     }
 
     private User getSimpleUser( org.apache.archiva.redback.users.User user )
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
index c7c2af3..d9ea256 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultAuthenticationService.java
@@ -23,20 +23,18 @@
 import org.apache.archiva.redback.authentication.AuthenticationException;
 import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
 import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
+import org.apache.archiva.redback.authentication.Token;
+import org.apache.archiva.redback.authentication.TokenType;
 import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
+import org.apache.archiva.redback.authentication.jwt.TokenAuthenticationException;
 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
-import org.apache.archiva.redback.keys.AuthenticationKey;
-import org.apache.archiva.redback.keys.KeyManager;
-import org.apache.archiva.redback.keys.jpa.model.JpaAuthenticationKey;
-import org.apache.archiva.redback.keys.memory.MemoryAuthenticationKey;
-import org.apache.archiva.redback.keys.memory.MemoryKeyManager;
 import org.apache.archiva.redback.policy.AccountLockedException;
 import org.apache.archiva.redback.policy.MustChangePasswordException;
-import org.apache.archiva.redback.rest.api.model.ActionStatus;
 import org.apache.archiva.redback.rest.api.model.ErrorMessage;
-import org.apache.archiva.redback.rest.api.model.LoginRequest;
 import org.apache.archiva.redback.rest.api.model.PingResult;
-import org.apache.archiva.redback.rest.api.model.Token;
+import org.apache.archiva.redback.rest.api.model.RefreshTokenRequest;
+import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
+import org.apache.archiva.redback.rest.api.model.TokenResponse;
 import org.apache.archiva.redback.rest.api.model.User;
 import org.apache.archiva.redback.rest.api.model.UserLogin;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
@@ -53,17 +51,11 @@
 import javax.inject.Named;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
-import java.time.Duration;
-import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
 import java.util.List;
-import java.util.TimeZone;
 
 /**
  *
@@ -106,37 +98,6 @@
 
 
     @Override
-    public Token requestOnetimeToken( String providedKey, String principal, String purpose, int expirationSeconds )
-    {
-        KeyManager keyManager = securitySystem.getKeyManager();
-        AuthenticationKey key;
-
-        if ( keyManager instanceof MemoryKeyManager )
-        {
-            key = new MemoryAuthenticationKey();
-        }
-        else
-        {
-            key = new JpaAuthenticationKey();
-        }
-
-        key.setKey( providedKey );
-        key.setForPrincipal( principal );
-        key.setPurpose( purpose );
-
-        Instant now = Instant.now( );
-        key.setDateCreated( Date.from( now ) );
-
-        if ( expirationSeconds >= 0 )
-        {
-            Duration expireDuration = Duration.ofSeconds( expirationSeconds );
-            key.setDateExpires( Date.from( now.plus( expireDuration ) ) );
-        }
-        keyManager.addKey( key );
-        return Token.of( key );
-    }
-
-    @Override
     public PingResult ping()
     {
         return new PingResult( true);
@@ -148,15 +109,11 @@
         return new PingResult( true );
     }
 
-    private Token getRestToken( org.apache.archiva.redback.authentication.Token token ) {
-        return Token.of( token.getData( ), token.getMetadata( ).created( ), token.getMetadata( ).validBefore( ), token.getMetadata( ).getUser( ), "rest-auth" );
-    }
-
     @Override
-    public Token logIn( LoginRequest loginRequest )
+    public TokenResponse logIn( RequestTokenRequest loginRequest )
         throws RedbackServiceException
     {
-        String userName = loginRequest.getUsername(), password = loginRequest.getPassword();
+        String userName = loginRequest.getUserId(), password = loginRequest.getPassword();
         PasswordBasedAuthenticationDataSource authDataSource =
             new PasswordBasedAuthenticationDataSource( userName, password );
         log.debug("Login for {}",userName);
@@ -177,8 +134,10 @@
                 }
                 // Stateless services no session
                 // httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) );
-                Token restToken = getRestToken( token );
-                return restToken;
+                org.apache.archiva.redback.authentication.Token refreshToken = jwtAuthenticator.generateToken( user.getUsername( ), TokenType.REFRESH_TOKEN );
+                response.setHeader( "Cache-Control", "no-store" );
+                response.setHeader( "Pragma", "no-cache" );
+                return new TokenResponse(token, refreshToken, "", loginRequest.getState());
             } else if ( securitySession.getAuthenticationResult() != null
                 && securitySession.getAuthenticationResult().getAuthenticationFailureCauses() != null )
             {
@@ -231,13 +190,25 @@
     }
 
     @Override
-    public Token renewToken( ) throws RedbackServiceException
+    public TokenResponse refreshToken( RefreshTokenRequest request ) throws RedbackServiceException
     {
-        return null;
+        if (!"refresh_token".equals(request.getGrantType().toLowerCase())) {
+            throw new RedbackServiceException( "redback:bad_grant", Response.Status.FORBIDDEN.getStatusCode( ) );
+        }
+        try
+        {
+            Token accessToken = jwtAuthenticator.refreshAccessToken( request.getRefreshToken( ) );
+            Token refreshToken = jwtAuthenticator.tokenFromString( request.getRefreshToken( ) );
+            return new TokenResponse( accessToken, refreshToken );
+        }
+        catch ( TokenAuthenticationException e )
+        {
+            throw new RedbackServiceException( e.getError( ).getError( ), Response.Status.UNAUTHORIZED.getStatusCode( ) );
+        }
     }
 
     @Override
-    public User isLogged()
+    public User getAuthenticatedUser()
         throws RedbackServiceException
     {
         SecuritySession securitySession = httpAuthenticator.getSecuritySession( httpServletRequest.getSession( true ) );
@@ -246,23 +217,6 @@
         return isLogged && securitySession.getUser() != null ? buildRestUser( securitySession.getUser() ) : null;
     }
 
-    @Override
-    public ActionStatus logout()
-        throws RedbackServiceException
-    {
-        HttpSession httpSession = httpServletRequest.getSession();
-        if ( httpSession != null )
-        {
-            httpSession.invalidate();
-        }
-        return ActionStatus.SUCCESS;
-    }
-
-    private Calendar getNowGMT()
-    {
-        return Calendar.getInstance( TimeZone.getTimeZone( "GMT" ) );
-    }
-
     private UserLogin buildRestUser( org.apache.archiva.redback.users.User user )
     {
         UserLogin restUser = new UserLogin();
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
index e1fc37d..11d73d9 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/LoginServiceTest.java
@@ -45,6 +45,13 @@
     }
 
     @Test
+    public void ping()
+        throws Exception
+    {
+        assertNotNull( getLoginService( null ).ping( ) );
+    }
+
+    @Test
     public void createUserThenLog()
         throws Exception
     {
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
index 555004e..ba5a78e 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
@@ -57,7 +57,7 @@
     public void ping()
         throws Exception
     {
-        Boolean res = getUserService().ping().isSuccess();
+        Boolean res = getUserService().ping();
         assertTrue( res.booleanValue() );
     }
 
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
new file mode 100644
index 0000000..2c24730
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java
@@ -0,0 +1,289 @@
+package org.apache.archiva.redback.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.specification.RequestSpecification;
+import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
+import org.apache.archiva.redback.rest.services.FakeCreateAdminServiceImpl;
+import org.apache.archiva.redback.role.RoleManager;
+import org.apache.archiva.redback.role.RoleManagerException;
+import org.apache.archiva.redback.users.User;
+import org.apache.archiva.redback.users.UserManager;
+import org.apache.archiva.redback.users.UserManagerException;
+import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.cxf.transport.servlet.CXFServlet;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.context.ContextLoaderListener;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static io.restassured.RestAssured.baseURI;
+import static io.restassured.RestAssured.port;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ *
+ * Native REST tests do not use the JAX-RS client and can be used with a remote
+ * REST API service. The tests
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@Tag("rest-native")
+public abstract class AbstractNativeRestServices
+{
+    public static final String SYSPROP_START_SERVER = "archiva.rest.start.server";
+    public static final String SYSPROP_SERVER_PORT = "archiva.rest.server.port";
+    public static final String SYSPROP_SERVER_BASE_URI = "archiva.rest.server.baseuri";
+    public static final int STOPPED = 0;
+    public static final int STOPPING = 1;
+    public static final int STARTING = 2;
+    public static final int STARTED = 3;
+    public static final int ERROR = 4;
+
+    private RequestSpecification requestSpec;
+    protected Logger log = LoggerFactory.getLogger( getClass() );
+
+    private static AtomicReference<Server> server = new AtomicReference<>();
+    private static AtomicReference<ServerConnector> serverConnector = new AtomicReference<>();
+    private static AtomicInteger serverStarted = new AtomicInteger( STOPPED );
+    private UserManager userManager;
+    private RoleManager roleManager;
+
+
+    protected abstract String getServicePath();
+
+    protected String getSpringConfigLocation()
+    {
+        return "classpath*:spring-context.xml,classpath*:META-INF/spring-context.xml";
+    }
+
+    protected RequestSpecification getRequestSpec() {
+        return this.requestSpec;
+    }
+
+    protected String getContextRoot()
+    {
+        return "/api";
+    }
+
+
+    private String getServiceBasePath( )
+    {
+        return "/v2/redback";
+    }
+
+    protected String getBasePath( )
+    {
+        return new StringBuilder(  )
+            .append(getContextRoot( ))
+            .append(getServiceBasePath( ))
+            .append(getServicePath( )).toString();
+    }
+
+    /**
+     * Returns the server that was started, or null if not initialized before.
+     * @return
+     */
+    public Server getServer() {
+        return this.server.get();
+    }
+
+    public int getServerPort() {
+        ServerConnector connector = serverConnector.get();
+        if (connector!=null) {
+            return connector.getLocalPort();
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Returns true, if the server does exist and is running.
+     * @return true, if server does exist and is running.
+     */
+    public boolean isServerRunning() {
+        return serverStarted.get()==STARTED && this.server.get() != null && this.server.get().isRunning();
+    }
+
+    private UserManager getUserManager() {
+        if (this.userManager==null) {
+            UserManager userManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+                .getBean( "userManager#default", UserManager.class );
+            assertNotNull( userManager );
+            this.userManager = userManager;
+        }
+        return this.userManager;
+    }
+
+    private RoleManager getRoleManager() {
+        if (this.roleManager==null) {
+            RoleManager roleManager = ContextLoaderListener.getCurrentWebApplicationContext( )
+                .getBean( "roleManager", RoleManager.class );
+            assertNotNull( roleManager );
+            this.roleManager = roleManager;
+        }
+        return this.roleManager;
+    }
+
+    private void setupAdminUser() throws UserManagerException, RoleManagerException
+    {
+        UserManager um = getUserManager( );
+
+        User adminUser = null;
+        try
+        {
+            adminUser = um.findUser( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME );
+        } catch ( UserNotFoundException e ) {
+            // ignore
+        }
+        if (adminUser==null)
+        {
+            adminUser = um.createUser( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME, "Administrator", "admin@local.home" );
+            adminUser.setUsername( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME );
+            adminUser.setPassword( FakeCreateAdminServiceImpl.ADMIN_TEST_PWD );
+            adminUser.setFullName( "the admin user" );
+            adminUser.setEmail( "toto@toto.fr" );
+            adminUser.setPermanent( true );
+            adminUser.setValidated( true );
+            adminUser.setLocked( false );
+            adminUser.setPasswordChangeRequired( false );
+            um.addUser( adminUser );
+
+            getRoleManager( ).assignRole( "system-administrator", adminUser.getUsername( ) );
+        }
+    }
+
+    public void startServer()
+        throws Exception
+    {
+        if (serverStarted.compareAndSet( STOPPED, STARTING ))
+        {
+            try
+            {
+                log.info( "Starting server" );
+                Server myServer = new Server( );
+                this.server.set( myServer );
+                this.serverConnector.set( new ServerConnector( myServer, new HttpConnectionFactory( ) ) );
+                myServer.addConnector( serverConnector.get( ) );
+
+                ServletHolder servletHolder = new ServletHolder( new CXFServlet( ) );
+                ServletContextHandler context = new ServletContextHandler( ServletContextHandler.SESSIONS );
+                context.setResourceBase( SystemUtils.JAVA_IO_TMPDIR );
+                context.setSessionHandler( new SessionHandler( ) );
+                context.addServlet( servletHolder, getContextRoot( ) + "/*" );
+                context.setInitParameter( "contextConfigLocation", getSpringConfigLocation( ) );
+                context.addEventListener( new ContextLoaderListener( ) );
+
+                getServer( ).setHandler( context );
+                getServer( ).start( );
+
+                if ( log.isDebugEnabled( ) )
+                {
+                    log.debug( "Jetty dump: {}", getServer( ).dump( ) );
+                }
+
+                setupAdminUser();
+                log.info( "Started server on port {}", getServerPort( ) );
+                serverStarted.set( STARTED );
+            } finally {
+                // In case, if the last statement was not reached
+                serverStarted.compareAndSet( STARTING, ERROR );
+            }
+        }
+
+    }
+
+    public void stopServer()
+        throws Exception
+    {
+        if ( this.serverStarted.compareAndSet( STARTED, STOPPING ) )
+        {
+            try
+            {
+                final Server myServer = getServer( );
+                if ( myServer != null )
+                {
+                    log.info("Stopping server");
+                    myServer.stop();
+                }
+                serverStarted.set( STOPPED );
+            } finally {
+                serverStarted.compareAndSet( STOPPING, ERROR );
+            }
+        } else {
+            log.error( "Serer is not in STARTED state!" );
+        }
+    }
+
+
+    protected void setupNative( ) throws Exception
+    {
+        String startServer = System.getProperty( SYSPROP_START_SERVER, "yes" ).toLowerCase( );
+        String serverPort = System.getProperty( SYSPROP_SERVER_PORT, "" );
+        String baseUri = System.getProperty( SYSPROP_SERVER_BASE_URI, "http://localhost" );
+
+        if ( !"no".equals( startServer ) )
+        {
+            startServer( );
+        }
+
+        if ( StringUtils.isNotEmpty( serverPort ) )
+        {
+            RestAssured.port = Integer.parseInt( serverPort );
+        }
+        else
+        {
+            RestAssured.port = getServerPort( );
+        }
+        if ( StringUtils.isNotEmpty( baseUri ) )
+        {
+            RestAssured.baseURI = baseUri;
+        }
+        else
+        {
+            RestAssured.baseURI = "http://localhost";
+        }
+        String basePath = getBasePath( );
+        RequestSpecBuilder builder = new RequestSpecBuilder( );
+        builder.setBaseUri( baseURI )
+            .setPort( port )
+            .setBasePath( basePath )
+            .addHeader( "Origin", RestAssured.baseURI + ":" + RestAssured.port );
+        this.requestSpec = builder.build( );
+        RestAssured.basePath = basePath;
+    }
+
+    protected void shutdownNative() throws Exception
+    {
+        stopServer();
+    }
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
index ccc9abe..a1419e1 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractRestServicesTestV2.java
@@ -22,10 +22,10 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
+import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
 import org.apache.archiva.redback.authentication.Token;
 import org.apache.archiva.redback.authentication.jwt.JwtAuthenticator;
 import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
-import org.apache.archiva.redback.rest.api.services.RoleManagementService;
 import org.apache.archiva.redback.rest.api.services.v2.AuthenticationService;
 import org.apache.archiva.redback.rest.services.FakeCreateAdminService;
 import org.apache.archiva.redback.rest.services.FakeCreateAdminServiceImpl;
@@ -56,6 +56,7 @@
 import javax.naming.NamingException;
 import javax.naming.directory.DirContext;
 import javax.ws.rs.core.MediaType;
+import java.text.SimpleDateFormat;
 import java.util.Collections;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -137,6 +138,8 @@
         JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider( );
         ObjectMapper mapper = new ObjectMapper( );
         mapper.registerModule( new JavaTimeModule( ) );
+        mapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector( mapper.getTypeFactory() ) );
+        mapper.setDateFormat( new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ) );
         provider.setMapper( mapper );
         return provider;
     }
@@ -227,7 +230,7 @@
 
     protected String getRestServicesPath()
     {
-        return "restServices";
+        return "api";
     }
 
     public void startServer()
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
index 9069a03..b507fc6 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AuthenticationServiceTest.java
@@ -20,7 +20,9 @@
 
 import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants;
 import org.apache.archiva.redback.rest.api.model.LoginRequest;
+import org.apache.archiva.redback.rest.api.model.RequestTokenRequest;
 import org.apache.archiva.redback.rest.api.model.Token;
+import org.apache.archiva.redback.rest.api.model.TokenResponse;
 import org.apache.archiva.redback.rest.api.services.RedbackServiceException;
 import org.apache.archiva.redback.rest.api.services.UserService;
 import org.apache.archiva.redback.rest.services.FakeCreateAdminService;
@@ -64,7 +66,7 @@
     public void loginAdmin()
         throws Exception
     {
-        assertNotNull( getLoginServiceV2( null ).logIn( new LoginRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
+        assertNotNull( getLoginServiceV2( null ).logIn( new RequestTokenRequest( RedbackRoleConstants.ADMINISTRATOR_ACCOUNT_NAME,
                                                                         FakeCreateAdminService.ADMIN_TEST_PWD ) ) );
     }
 
@@ -117,8 +119,8 @@
             user.setPasswordChangeRequired( false );
             um.updateUser( user );
             // END SNIPPET: create-user
-            LoginRequest request = new LoginRequest( "toto", "foo123" );
-            Token result = getLoginServiceV2( "" ).logIn( request );
+            RequestTokenRequest request = new RequestTokenRequest( "toto", "foo123" );
+            TokenResponse result = getLoginServiceV2( "" ).logIn( request );
             // assertNotNull( result );
             // assertEquals( "toto", result.getUsername( ) );
 
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
new file mode 100644
index 0000000..2a5182a
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeAuthenticationServiceTest.java
@@ -0,0 +1,90 @@
+package org.apache.archiva.redback.rest.services.v2;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import io.restassured.RestAssured;
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+
+import static io.restassured.RestAssured.*;
+import static io.restassured.http.ContentType.JSON;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.springframework.core.annotation.MergedAnnotations.from;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@ExtendWith( SpringExtension.class )
+@ContextConfiguration(
+    locations = {"classpath:/ldap-spring-test.xml"} )
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@Tag( "rest-native" )
+public class NativeAuthenticationServiceTest extends AbstractNativeRestServices
+{
+
+    @Override
+    protected String getServicePath( )
+    {
+        return "/auth";
+    }
+
+    @BeforeAll
+    void setup( ) throws Exception
+    {
+        setupNative( );
+    }
+
+    @AfterAll
+    void shutdown( ) throws Exception
+    {
+        shutdownNative();
+    }
+
+    @Test
+    void ping( )
+    {
+        Instant beforeCall = Instant.now( );
+        Response response = given( ).spec( getRequestSpec() )
+            .when( ).get( "/ping" )
+            .then( ).assertThat( ).statusCode( 200 ).and( )
+            .contentType( JSON ).
+                body( "success", equalTo( true ) )
+            .body( "requestTime", notNullValue( ) ).extract().response();
+        OffsetDateTime dateTime = OffsetDateTime.parse( response.body( ).jsonPath( ).getString( "requestTime" ) );
+        Instant afterCall = Instant.now( );
+        assertTrue( dateTime.toInstant( ).isAfter( beforeCall ) );
+        assertTrue( dateTime.toInstant( ).isBefore( afterCall ) );
+    }
+
+}
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
index 5f8bb09..e058bdb 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/resources/log4j2-test.xml
@@ -31,7 +31,7 @@
     <logger name="org.springframework" level="error"/>
     <logger name="org.apache.archiva.redback.components.cache" level="error"/>
     <logger name="org.apache.archiva.redback.rest.services.interceptors" level="debug"/>
-    <logger name="org.apache.archiva.redback.rest.services" level="info"/>
+    <logger name="org.apache.archiva.redback.rest.services" level="debug"/>
     <logger name="org.apache.catalina" level="off" />
     <logger name="JPOX" level="ERROR"/>
     <root level="info">