diff --git a/api/.env.defaults b/api/.env.defaults
index c6b1dbd100c3a53dd9aec8f09c0db9704b962bb9..f11df677fee167b13856039b06d92a627a815bc4 100644
--- a/api/.env.defaults
+++ b/api/.env.defaults
@@ -6,16 +6,4 @@ ENGINE_BASE_URL=http://127.0.0.1:8080/services/
 TOS_SKIP=false
 
 # SERVER
-GATEWAY_PORT=8081
-
-# AUTHENTICATION
-AUTH_SKIP=false
-AUTH_JWT_SECRET=SecretForDevPurposeOnly
-AUTH_JWT_TOKEN_EXPIRES_IN=2d
-AUTH_COOKIE_SAME_SITE=strict
-AUTH_COOKIE_SECURE=true
-
-# Cache
-CACHE_ENABLED=true
-CACHE_TTL=1800 # 1800 == 30 minutes
-CACHE_MAX_ITEMS=100
\ No newline at end of file
+GATEWAY_PORT=8081
\ No newline at end of file
diff --git a/api/package-lock.json b/api/package-lock.json
index 74d865159a05949fc6f0951cf37a5f60a21ee877..389d9d380f6ee55562b32fce2f7c0e092a8a135e 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -19,6 +19,7 @@
         "@nestjs/passport": "^8.2.1",
         "@nestjs/platform-express": "^8.0.0",
         "@nestjs/typeorm": "^8.0.3",
+        "@types/object-hash": "^2.2.1",
         "apollo-server-express": "^3.6.3",
         "axios": "^0.21.1",
         "cache-manager": "^4.0.1",
@@ -26,6 +27,7 @@
         "graphql": "^15.5.3",
         "graphql-type-json": "^0.3.2",
         "jsonata": "^1.8.5",
+        "object-hash": "^3.0.0",
         "passport": "^0.5.2",
         "passport-custom": "^1.1.1",
         "passport-jwt": "^4.0.0",
@@ -2773,6 +2775,11 @@
       "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
       "dev": true
     },
+    "node_modules/@types/object-hash": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-2.2.1.tgz",
+      "integrity": "sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ=="
+    },
     "node_modules/@types/parse-json": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -17552,6 +17559,11 @@
       "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
       "dev": true
     },
+    "@types/object-hash": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-2.2.1.tgz",
+      "integrity": "sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ=="
+    },
     "@types/parse-json": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
diff --git a/api/package.json b/api/package.json
index 41ac9d65ba74232143c5a2d9db79e93ef7ba5e2d..78c2d6d8c55a1a4977f77394de8becf0408c70c7 100644
--- a/api/package.json
+++ b/api/package.json
@@ -36,6 +36,7 @@
     "@nestjs/passport": "^8.2.1",
     "@nestjs/platform-express": "^8.0.0",
     "@nestjs/typeorm": "^8.0.3",
+    "@types/object-hash": "^2.2.1",
     "apollo-server-express": "^3.6.3",
     "axios": "^0.21.1",
     "cache-manager": "^4.0.1",
@@ -43,6 +44,7 @@
     "graphql": "^15.5.3",
     "graphql-type-json": "^0.3.2",
     "jsonata": "^1.8.5",
+    "object-hash": "^3.0.0",
     "passport": "^0.5.2",
     "passport-custom": "^1.1.1",
     "passport-jwt": "^4.0.0",
diff --git a/api/src/auth/auth-constants.ts b/api/src/auth/auth-constants.ts
deleted file mode 100644
index 5e4748c1e52bca63b765b555568f6da6d6bb9d85..0000000000000000000000000000000000000000
--- a/api/src/auth/auth-constants.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export const authConstants = {
-  JWTSecret: 'AUTH_JWT_SECRET',
-  skipAuth: 'AUTH_SKIP',
-  expiresIn: 'AUTH_JWT_TOKEN_EXPIRES_IN',
-  enableSSO: 'AUTH_ENABLE_SSO',
-  cookie: {
-    name: 'jwt-gateway',
-    sameSite: 'AUTH_COOKIE_SAME_SITE',
-    secure: 'AUTH_COOKIE_SECURE',
-    httpOnly: 'AUTH_COOKIE_HTTPONLY',
-  },
-};
diff --git a/api/src/auth/auth.module.ts b/api/src/auth/auth.module.ts
index 936e09cab00920b1f9d4494200b10cce62fb8b06..b84f68ffff7c94834b11d1f12d57ec8b1237c1b7 100644
--- a/api/src/auth/auth.module.ts
+++ b/api/src/auth/auth.module.ts
@@ -1,8 +1,9 @@
 import { Module } from '@nestjs/common';
-import { ConfigModule, ConfigService } from '@nestjs/config';
+import { ConfigModule, ConfigService, ConfigType } from '@nestjs/config';
 import { JwtModule } from '@nestjs/jwt';
 import { PassportModule } from '@nestjs/passport';
-import { authConstants } from './auth-constants';
+import authConfig from 'src/config/auth.config';
+import { UsersModule } from 'src/users/users.module';
 import { AuthResolver } from './auth.resolver';
 import { AuthService } from './auth.service';
 import { EngineStrategy } from './strategies/engine.strategy';
@@ -12,17 +13,22 @@ import { LocalStrategy } from './strategies/local.strategy';
 
 @Module({
   imports: [
+    UsersModule,
     PassportModule.register({
       session: false,
     }),
     JwtModule.registerAsync({
       imports: [ConfigModule],
-      useFactory: async (configService: ConfigService) => ({
-        secret: configService.get(authConstants.JWTSecret),
-        signOptions: {
-          expiresIn: configService.get(authConstants.expiresIn, '2d'),
-        },
-      }),
+      useFactory: async (configService: ConfigService) => {
+        const authConf =
+          configService.get<ConfigType<typeof authConfig>>('auth');
+        return {
+          secret: authConf.JWTSecret,
+          signOptions: {
+            expiresIn: authConf.expiresIn,
+          },
+        };
+      },
       inject: [ConfigService],
     }),
   ],
diff --git a/api/src/auth/auth.resolver.spec.ts b/api/src/auth/auth.resolver.spec.ts
index 7890a9e5bc8f52774599a6a66a14a9a54ee75a04..1c660e25244b2580cb40a51c5344ca1617dafb65 100644
--- a/api/src/auth/auth.resolver.spec.ts
+++ b/api/src/auth/auth.resolver.spec.ts
@@ -1,11 +1,12 @@
-import { getMockRes } from '@jest-mock/express';
+import { getMockReq, getMockRes } from '@jest-mock/express';
+import { ConfigType } from '@nestjs/config';
 import { Test, TestingModule } from '@nestjs/testing';
+import { Request } from 'express';
 import { MockFunctionMetadata, ModuleMocker } from 'jest-mock';
-import EngineService from '../engine/engine.service';
-import LocalService from '../engine/connectors/local/local.connector';
+import authConfig from '../config/auth.config';
 import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants';
+import EngineService from '../engine/engine.service';
 import { User } from '../users/models/user.model';
-import { authConstants } from './auth-constants';
 import { AuthResolver } from './auth.resolver';
 import { AuthService } from './auth.service';
 
@@ -14,8 +15,10 @@ const moduleMocker = new ModuleMocker(global);
 describe('AuthResolver', () => {
   let resolver: AuthResolver;
   const { res } = getMockRes();
+  const req: Request = getMockReq();
   const mockCookie = jest.fn();
   const mockClearCookie = jest.fn();
+  let config: ConfigType<typeof authConfig>;
 
   res.cookie = mockCookie;
   res.clearCookie = mockClearCookie;
@@ -37,9 +40,15 @@ describe('AuthResolver', () => {
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
       providers: [
+        {
+          provide: authConfig.KEY,
+          useValue: authConfig(),
+        },
         {
           provide: EngineService,
-          useClass: LocalService,
+          useValue: {
+            clearCache: jest.fn(),
+          },
         },
         {
           provide: ENGINE_MODULE_OPTIONS,
@@ -67,12 +76,13 @@ describe('AuthResolver', () => {
       .compile();
 
     resolver = module.get<AuthResolver>(AuthResolver);
+    config = module.get<ConfigType<typeof authConfig>>(authConfig.KEY);
   });
 
   it('login', async () => {
-    const data = await resolver.login(res, user, credentials);
+    const data = await resolver.login(res, req, user, credentials);
 
-    expect(mockCookie.mock.calls[0][0]).toBe(authConstants.cookie.name);
+    expect(mockCookie.mock.calls[0][0]).toBe(config.cookie.name);
     expect(mockCookie.mock.calls[0][1]).toBe(authData.accessToken);
     expect(data.accessToken).toBe(authData.accessToken);
   });
@@ -81,6 +91,6 @@ describe('AuthResolver', () => {
     const request: any = jest.fn();
     await resolver.logout(request, res, user);
 
-    expect(mockClearCookie.mock.calls[0][0]).toBe(authConstants.cookie.name);
+    expect(mockClearCookie.mock.calls[0][0]).toBe(config.cookie.name);
   });
 });
diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts
index cf3208a99b76554b383a8dd3c181805f38d9ce06..27c2694fc1d1a07873d3e18a5927ce2bc5359f0e 100644
--- a/api/src/auth/auth.resolver.ts
+++ b/api/src/auth/auth.resolver.ts
@@ -4,23 +4,23 @@ import {
   Logger,
   UseGuards,
 } from '@nestjs/common';
-import { ConfigService } from '@nestjs/config';
+import { ConfigType } from '@nestjs/config';
 import { Args, Mutation, Resolver } from '@nestjs/graphql';
-import { Response, Request } from 'express';
-import { CurrentUser } from '../common/decorators/user.decorator';
+import { Request, Response } from 'express';
 import { GQLRequest } from '../common/decorators/gql-request.decoractor';
 import { GQLResponse } from '../common/decorators/gql-response.decoractor';
+import { CurrentUser } from '../common/decorators/user.decorator';
+import { parseToBoolean } from '../common/utils/shared.utils';
+import authConfig from '../config/auth.config';
 import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants';
+import EngineService from '../engine/engine.service';
+import EngineOptions from '../engine/interfaces/engine-options.interface';
 import { User } from '../users/models/user.model';
-import { authConstants } from './auth-constants';
 import { AuthService } from './auth.service';
 import { GlobalAuthGuard } from './guards/global-auth.guard';
 import { LocalAuthGuard } from './guards/local-auth.guard';
 import { AuthenticationInput } from './inputs/authentication.input';
 import { AuthenticationOutput } from './outputs/authentication.output';
-import { parseToBoolean } from '../common/utils/shared.utils';
-import EngineOptions from '../engine/interfaces/engine-options.interface';
-import EngineService from '../engine/engine.service';
 
 //Custom defined type because Pick<CookieOptions, 'sameSite'> does not work
 type SameSiteType = boolean | 'lax' | 'strict' | 'none' | undefined;
@@ -34,40 +34,52 @@ export class AuthResolver {
     @Inject(ENGINE_MODULE_OPTIONS)
     private readonly engineOptions: EngineOptions,
     private readonly authService: AuthService,
-    private readonly configService: ConfigService,
+    @Inject(authConfig.KEY) private authConf: ConfigType<typeof authConfig>,
   ) {}
 
   @Mutation(() => AuthenticationOutput)
   @UseGuards(LocalAuthGuard)
   async login(
     @GQLResponse() res: Response,
+    @GQLRequest() req: Request,
     @CurrentUser() user: User,
     @Args('variables') inputs: AuthenticationInput,
   ): Promise<AuthenticationOutput> {
     this.logger.verbose(`${inputs.username} logged in`);
 
+    this.engineService.clearCache(req);
+
     const data = await this.authService.login(user);
     if (!data)
       throw new InternalServerErrorException(
         `Error during the authentication process`,
       );
 
-    res.cookie(authConstants.cookie.name, data.accessToken, {
-      httpOnly: parseToBoolean(
-        this.configService.get(authConstants.cookie.httpOnly, 'true'),
-      ),
-      sameSite: this.configService.get<SameSiteType>(
-        authConstants.cookie.sameSite,
-        'strict',
-      ),
-      secure: parseToBoolean(
-        this.configService.get(authConstants.cookie.secure, 'true'),
-      ),
+    res.cookie(this.authConf.cookie.name, data.accessToken, {
+      httpOnly: parseToBoolean(this.authConf.cookie.httpOnly),
+      sameSite: this.authConf.cookie.sameSite as SameSiteType,
+      secure: parseToBoolean(this.authConf.cookie.secure),
+    });
+
+    return data;
+  }
+
+  @Mutation(() => AuthenticationOutput)
+  async refresh(
+    @GQLResponse() res: Response,
+    @Args('refreshToken', { type: () => String }) refreshToken: string,
+  ): Promise<AuthenticationOutput> {
+    const data = await this.authService.createTokensWithRefreshToken(
+      refreshToken,
+    );
+
+    res.cookie(this.authConf.cookie.name, data.accessToken, {
+      httpOnly: parseToBoolean(this.authConf.cookie.httpOnly),
+      sameSite: this.authConf.cookie.sameSite as SameSiteType,
+      secure: parseToBoolean(this.authConf.cookie.secure),
     });
 
-    return {
-      accessToken: data.accessToken,
-    };
+    return data;
   }
 
   @Mutation(() => Boolean)
@@ -83,14 +95,16 @@ export class AuthResolver {
         if (this.engineService.has('logout')) {
           await this.engineService.logout(req);
         }
+        this.authService.logout(user);
       } catch (e) {
-        this.logger.debug(
+        this.logger.warn(
           `Service ${this.engineOptions.type} produce an error when logging out ${user.username}`,
         );
+        this.logger.debug(e);
       }
     }
 
-    res.clearCookie(authConstants.cookie.name);
+    res.clearCookie(this.authConf.cookie.name);
 
     return true;
   }
diff --git a/api/src/auth/auth.service.spec.ts b/api/src/auth/auth.service.spec.ts
index ea689bdf469a81b7e8bdc4d9429a87598406d05c..0e92ef5b9e4892fddc2474ab690f6c3d7e5e7ca5 100644
--- a/api/src/auth/auth.service.spec.ts
+++ b/api/src/auth/auth.service.spec.ts
@@ -5,6 +5,7 @@ import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants';
 import { AuthService } from './auth.service';
 import { User } from '../users/models/user.model';
 import EngineService from '../engine/engine.service';
+import authConfig from '../config/auth.config';
 
 const moduleMocker = new ModuleMocker(global);
 
@@ -27,6 +28,10 @@ describe('AuthService', () => {
   beforeEach(async () => {
     const module: TestingModule = await Test.createTestingModule({
       providers: [
+        {
+          provide: authConfig.KEY,
+          useValue: authConfig(),
+        },
         {
           provide: EngineService,
           useValue: createEngineService(),
@@ -45,6 +50,7 @@ describe('AuthService', () => {
         if (token === JwtService) {
           return {
             sign: jest.fn().mockReturnValue(jwtToken),
+            signAsync: jest.fn().mockResolvedValue(jwtToken),
           };
         }
         if (typeof token === 'function') {
diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts
index 8300b9a1649ce7683d0559c2814dbde94fec16bc..fd2110714baf44e5f64f12dfd059039b2f6dfa15 100644
--- a/api/src/auth/auth.service.ts
+++ b/api/src/auth/auth.service.ts
@@ -1,14 +1,32 @@
-import { Injectable, NotImplementedException } from '@nestjs/common';
-import { JwtService } from '@nestjs/jwt';
+import {
+  Inject,
+  Injectable,
+  Logger,
+  NotImplementedException,
+  UnauthorizedException,
+} from '@nestjs/common';
+import { ConfigType } from '@nestjs/config';
+import { JwtService, JwtSignOptions } from '@nestjs/jwt';
+import { UsersService } from '../users/users.service';
+import authConfig from '../config/auth.config';
 import EngineService from '../engine/engine.service';
 import { User } from '../users/models/user.model';
 import { AuthenticationOutput } from './outputs/authentication.output';
+import * as hashing from 'object-hash';
+
+type TokenPayload = {
+  context: User;
+};
 
 @Injectable()
 export class AuthService {
+  private static readonly logger = new Logger(AuthService.name);
+
   constructor(
+    @Inject(authConfig.KEY) private authConf: ConfigType<typeof authConfig>,
     private readonly engineService: EngineService,
-    private jwtService: JwtService,
+    private readonly usersService: UsersService,
+    private readonly jwtService: JwtService,
   ) {}
 
   async validateUser(username: string, password: string): Promise<User> {
@@ -17,14 +35,86 @@ export class AuthService {
   }
 
   /**
-   * It takes a user and returns an access token
+   * It takes a user and returns an access and refresh tokens
    * @param {User} user - The user object that is being authenticated.
    * @returns An object with an accessToken property.
    */
-  async login(user: User): Promise<Pick<AuthenticationOutput, 'accessToken'>> {
-    const payload = { username: user.username, sub: user };
-    return Promise.resolve({
-      accessToken: this.jwtService.sign(payload),
+  async login(user: User): Promise<AuthenticationOutput> {
+    const payload: TokenPayload = {
+      context: {
+        ...user,
+        refreshToken: undefined,
+        agreeNDA: undefined,
+      },
+    };
+
+    const [accessToken, refreshToken] = await Promise.all([
+      this.jwtService.signAsync(payload),
+      this.jwtService.signAsync(payload, this.getRefreshTokenOptions()),
+    ]);
+
+    const hashRefresh = await this.getHash(refreshToken);
+
+    this.usersService.update(user.id, {
+      refreshToken: hashRefresh,
     });
+
+    return {
+      accessToken,
+      refreshToken,
+    };
+  }
+
+  /**
+   * Logout the user by deleting the refresh token from the database.
+   * @param {User} user - User object that is being logged out.
+   */
+  async logout(user: User): Promise<void> {
+    try {
+      if (user.id)
+        await this.usersService.update(user.id, { refreshToken: null });
+    } catch (err) {
+      //user not found or others errors
+      AuthService.logger.debug('Error while logging out user', err);
+    }
+  }
+
+  async createTokensWithRefreshToken(
+    refreshToken: string,
+  ): Promise<AuthenticationOutput> {
+    try {
+      const payload = this.jwtService.verify<TokenPayload>(
+        refreshToken,
+        this.getRefreshTokenOptions(),
+      );
+      const user = await this.usersService.findOne(payload.context.id);
+      const isMatchingTokens =
+        user.refreshToken === (await this.getHash(refreshToken));
+
+      if (!isMatchingTokens) {
+        this.logout(payload.context);
+        throw new UnauthorizedException();
+      }
+      return this.login(payload.context);
+    } catch (error) {
+      throw new UnauthorizedException('Invalid refresh token');
+    }
+  }
+
+  /**
+   * Make a hash out of an object. This function is not cryptographically secure
+   * and should only be used for hashing purposes.
+   * @param {any} obj - The object to be hashed.
+   * @returns A promise that resolves to a string.
+   */
+  private async getHash(obj: any): Promise<string> {
+    return hashing(obj);
+  }
+
+  private getRefreshTokenOptions(): JwtSignOptions {
+    return {
+      expiresIn: this.authConf.refreshExperiesIn,
+      secret: this.authConf.JWTResfreshSecret,
+    };
   }
 }
diff --git a/api/src/auth/guards/global-auth.guard.ts b/api/src/auth/guards/global-auth.guard.ts
index ecfd65e01ac3ae1a09b452ca74ff86b62cc27b7d..7a210b3dff67b09effdd6119e804f5c001347634 100644
--- a/api/src/auth/guards/global-auth.guard.ts
+++ b/api/src/auth/guards/global-auth.guard.ts
@@ -1,4 +1,8 @@
-import { ExecutionContext, Injectable } from '@nestjs/common';
+import {
+  ExecutionContext,
+  Injectable,
+  UnauthorizedException,
+} from '@nestjs/common';
 import { ConfigService } from '@nestjs/config';
 import { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
@@ -18,6 +22,14 @@ export class GlobalAuthGuard extends AuthGuard([
     super();
   }
 
+  handleRequest<TUser = any>(err: any, user: any): TUser {
+    if (err || !user) {
+      throw new UnauthorizedException();
+    }
+
+    return user;
+  }
+
   getRequest(context: ExecutionContext) {
     const ctx = GqlExecutionContext.create(context);
     const gqlReq = ctx.getContext().req;
diff --git a/api/src/auth/outputs/authentication.output.ts b/api/src/auth/outputs/authentication.output.ts
index f6a3dabfb1fcb672ae57129854fc6f8c26d3b9c8..bcefaee22cabf051a35b55c7f036c10f19773541 100644
--- a/api/src/auth/outputs/authentication.output.ts
+++ b/api/src/auth/outputs/authentication.output.ts
@@ -4,4 +4,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
 export class AuthenticationOutput {
   @Field()
   accessToken: string;
+
+  @Field()
+  refreshToken: string;
 }
diff --git a/api/src/auth/strategies/jwt-bearer.strategy.ts b/api/src/auth/strategies/jwt-bearer.strategy.ts
index 9439c87cf54accd4684455c1ce66df564d3af2ac..4a036ec1094258c2558b333d56b0a0956ce36b43 100644
--- a/api/src/auth/strategies/jwt-bearer.strategy.ts
+++ b/api/src/auth/strategies/jwt-bearer.strategy.ts
@@ -1,23 +1,25 @@
-import { Injectable } from '@nestjs/common';
-import { ConfigService } from '@nestjs/config';
+import { Inject, Injectable } from '@nestjs/common';
+import { ConfigType } from '@nestjs/config';
 import { PassportStrategy } from '@nestjs/passport';
 import { ExtractJwt, Strategy } from 'passport-jwt';
-import { authConstants } from '../auth-constants';
+import authConfig from 'src/config/auth.config';
 
 @Injectable()
 export class JwtBearerStrategy extends PassportStrategy(
   Strategy,
   'jwt-bearer',
 ) {
-  constructor(private readonly configService: ConfigService) {
+  constructor(
+    @Inject(authConfig.KEY) private authConf: ConfigType<typeof authConfig>,
+  ) {
     super({
       jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
       ignoreExpiration: false,
-      secretOrKey: configService.get<string>(authConstants.JWTSecret),
+      secretOrKey: authConf.JWTSecret,
     });
   }
 
   async validate(payload: any) {
-    return payload.sub;
+    return payload.context;
   }
 }
diff --git a/api/src/auth/strategies/jwt-cookies.strategy.ts b/api/src/auth/strategies/jwt-cookies.strategy.ts
index 29ca031c34660149bb3a90e30ebed97432f7e9ca..1fb31ca54985d3d4eb48ae00b494f6dc248dbabc 100644
--- a/api/src/auth/strategies/jwt-cookies.strategy.ts
+++ b/api/src/auth/strategies/jwt-cookies.strategy.ts
@@ -1,32 +1,34 @@
-import { Injectable } from '@nestjs/common';
-import { ConfigService } from '@nestjs/config';
+import { Inject, Injectable } from '@nestjs/common';
+import { ConfigType } from '@nestjs/config';
 import { PassportStrategy } from '@nestjs/passport';
 import { Request } from 'express';
 import { Strategy } from 'passport-jwt';
-import { authConstants } from '../auth-constants';
+import authConfig from 'src/config/auth.config';
 
 @Injectable()
 export class JwtCookiesStrategy extends PassportStrategy(
   Strategy,
   'jwt-cookies',
 ) {
-  constructor(private readonly configService: ConfigService) {
+  constructor(
+    @Inject(authConfig.KEY) private authConf: ConfigType<typeof authConfig>,
+  ) {
     super({
       jwtFromRequest: JwtCookiesStrategy.extractFromCookie,
       ignoreExpiration: false,
-      secretOrKey: configService.get<string>(authConstants.JWTSecret),
+      secretOrKey: authConf.JWTSecret,
     });
   }
 
   static extractFromCookie = function (req: Request) {
     let token = null;
     if (req && req.cookies) {
-      token = req.cookies[authConstants.cookie.name];
+      token = req.cookies[this.authConf.cookie.name];
     }
     return token;
   };
 
   async validate(payload: any) {
-    return payload.sub;
+    return payload.context;
   }
 }
diff --git a/api/src/config/auth.config.ts b/api/src/config/auth.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..85bf65d0956383b1eb011b5d2a9f0f456ada4ed8
--- /dev/null
+++ b/api/src/config/auth.config.ts
@@ -0,0 +1,16 @@
+import { registerAs } from '@nestjs/config';
+
+export default registerAs('auth', () => ({
+  JWTSecret: process.env.AUTH_JWT_SECRET || 'access-secret',
+  JWTResfreshSecret: process.env.AUTH_JWT_REFRESH_SECRET || 'refresh-secret',
+  skipAuth: process.env.AUTH_SKIP || 'false',
+  expiresIn: process.env.AUTH_JWT_TOKEN_EXPIRES_IN || '1h',
+  refreshExperiesIn: process.env.AUTH_JWT_REFRESH_TOKEN_EXPIRES_IN || '2d',
+  enableSSO: process.env.AUTH_ENABLE_SSO || 'false',
+  cookie: {
+    name: process.env.AUTH_COOKIE_NAME || 'jwt-gateway',
+    sameSite: process.env.AUTH_COOKIE_SAME_SITE || 'strict',
+    secure: process.env.AUTH_COOKIE_SECURE || 'false',
+    httpOnly: process.env.AUTH_COOKIE_HTTPONLY || 'true',
+  },
+}));
diff --git a/api/src/config/cache.config.ts b/api/src/config/cache.config.ts
index 763d7d5b3bcc73dbdfeabdebdba94f4837b1f8ed..c99534d7c4168bf3e2bb2bf4a9c3b17933a2e83e 100644
--- a/api/src/config/cache.config.ts
+++ b/api/src/config/cache.config.ts
@@ -5,8 +5,8 @@ export default registerAs('cache', () => {
   const max = process.env.CACHE_MAX_ITEMS;
   const ttl = process.env.CACHE_TTL;
   return {
-    enabled: parseToBoolean(process.env.CACHE_ENABLED, false),
-    ttl: ttl ? parseInt(ttl) : undefined,
-    max: max ? parseInt(max) : undefined,
+    enabled: parseToBoolean(process.env.CACHE_ENABLED, true),
+    ttl: ttl ? parseInt(ttl) : 1800,
+    max: max ? parseInt(max) : 100,
   };
 });
diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts
index cb0ffac9dc36e5e358959e18a412ac980a7cc7ac..97b39c736ff064772fc7df701a404965dae187db 100644
--- a/api/src/engine/engine.resolver.ts
+++ b/api/src/engine/engine.resolver.ts
@@ -1,12 +1,12 @@
 import { Inject, UseGuards, UseInterceptors } from '@nestjs/common';
-import { ConfigService } from '@nestjs/config';
+import { ConfigService, ConfigType } from '@nestjs/config';
 import { Query, Resolver } from '@nestjs/graphql';
 import { Request } from 'express';
 import { Public } from 'src/auth/decorators/public.decorator';
 import { GlobalAuthGuard } from 'src/auth/guards/global-auth.guard';
 import { parseToBoolean } from 'src/common/utils/shared.utils';
+import authConfig from 'src/config/auth.config';
 import { Md5 } from 'ts-md5';
-import { authConstants } from '../auth/auth-constants';
 import { GQLRequest } from '../common/decorators/gql-request.decoractor';
 import {
   ENGINE_CONTACT_LINK,
@@ -37,20 +37,17 @@ export class EngineResolver {
   @Query(() => Configuration)
   @Public()
   configuration(): Configuration {
-    const config = this.engineService.getConfiguration();
+    const engineConf = this.engineService.getConfiguration();
     const matomo = this.configSerivce.get('matomo');
+    const authConf: ConfigType<typeof authConfig> =
+      this.configSerivce.get('auth');
 
     const data = {
-      ...(config ?? {}),
+      ...(engineConf ?? {}),
       connectorId: this.engineOptions.type,
       skipTos: parseToBoolean(this.configSerivce.get(ENGINE_SKIP_TOS)),
-      enableSSO: parseToBoolean(
-        this.configSerivce.get(authConstants.enableSSO),
-      ),
-      skipAuth: parseToBoolean(
-        this.configSerivce.get(authConstants.skipAuth),
-        true,
-      ),
+      enableSSO: parseToBoolean(authConf.enableSSO),
+      skipAuth: parseToBoolean(authConf.skipAuth, true),
       matomo,
       ontologyUrl: this.configSerivce.get(ENGINE_ONTOLOGY_URL),
       contactLink: this.configSerivce.get(ENGINE_CONTACT_LINK),
diff --git a/api/src/engine/engine.service.ts b/api/src/engine/engine.service.ts
index 70a7180033d724421c44589f206cca935130f9c4..35b3cc39db577982f63605f6457ec3929d9b7f90 100644
--- a/api/src/engine/engine.service.ts
+++ b/api/src/engine/engine.service.ts
@@ -221,11 +221,10 @@ export default class EngineService implements Connector {
     return this.connector.getFilterConfiguration(req);
   }
 
-  async logout?(req: Request): Promise<void> {
+  async logout(req: Request): Promise<void> {
     await this.clearCache(req);
 
-    if (!this.connector.logout) throw new NotImplementedException();
-    return this.connector.logout(req);
+    if (this.connector.logout) this.connector.logout(req);
   }
 
   /**
diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts
index 5c113466f7233d9ff30419b48c9c495e60ae9f66..6b2415689fa0cfb23f66596e388e8ee3d4307e0d 100644
--- a/api/src/main/app.module.ts
+++ b/api/src/main/app.module.ts
@@ -6,6 +6,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 import { GraphQLError } from 'graphql';
 import { join } from 'path';
 import { AuthModule } from 'src/auth/auth.module';
+import authConfig from 'src/config/auth.config';
 import cacheConfig from 'src/config/cache.config';
 import dbConfig from 'src/config/db.config';
 import matomoConfig from 'src/config/matomo.config';
@@ -21,7 +22,7 @@ import { AppService } from './app.service';
     ConfigModule.forRoot({
       isGlobal: true,
       envFilePath: ['.env', '.env.defaults'],
-      load: [dbConfig, matomoConfig, cacheConfig],
+      load: [dbConfig, matomoConfig, cacheConfig, authConfig],
     }),
     GraphQLModule.forRoot<ApolloDriverConfig>({
       driver: ApolloDriver,
@@ -59,8 +60,8 @@ import { AppService } from './app.service';
         autoLoadEntities: true,
       }),
     }),
-    AuthModule,
     UsersModule,
+    AuthModule,
     ExperimentsModule,
     FilesModule,
   ],
diff --git a/api/src/migrations/1659096310828-UsersRefreshToken.ts b/api/src/migrations/1659096310828-UsersRefreshToken.ts
new file mode 100644
index 0000000000000000000000000000000000000000..90bb3dbb95145f17421d33640c0f90a99dcaa670
--- /dev/null
+++ b/api/src/migrations/1659096310828-UsersRefreshToken.ts
@@ -0,0 +1,15 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class UsersRefreshToken1659096310828 implements MigrationInterface {
+  name = 'UsersRefreshToken1659096310828';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      `ALTER TABLE "user" ADD "refreshToken" character varying`,
+    );
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "refreshToken"`);
+  }
+}
diff --git a/api/src/schema.gql b/api/src/schema.gql
index 3720e78944a93b077e3e7458d088bf4c562dda53..913fd6dca93e03650a13765141ebf554c3fbd318 100644
--- a/api/src/schema.gql
+++ b/api/src/schema.gql
@@ -23,6 +23,7 @@ type User {
 
 type AuthenticationOutput {
   accessToken: String!
+  refreshToken: String!
 }
 
 type Matomo {
@@ -385,23 +386,24 @@ type Query {
 }
 
 type Mutation {
+  updateUser(updateUserInput: UpdateUserInput!): User!
   login(variables: AuthenticationInput!): AuthenticationOutput!
+  refresh(refreshToken: String!): AuthenticationOutput!
   logout: Boolean!
-  updateUser(updateUserInput: UpdateUserInput!): User!
   createExperiment(data: ExperimentCreateInput!, isTransient: Boolean = false): Experiment!
   editExperiment(id: String!, data: ExperimentEditInput!): Experiment!
   removeExperiment(id: String!): PartialExperiment!
 }
 
+input UpdateUserInput {
+  agreeNDA: Boolean!
+}
+
 input AuthenticationInput {
   username: String!
   password: String!
 }
 
-input UpdateUserInput {
-  agreeNDA: Boolean!
-}
-
 input ExperimentCreateInput {
   datasets: [String!]!
   filter: String
diff --git a/api/src/users/decorators/extend-user.decorator.ts b/api/src/users/decorators/extend-user.decorator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1912708faa011ab3939fa15099ffcf199394bc07
--- /dev/null
+++ b/api/src/users/decorators/extend-user.decorator.ts
@@ -0,0 +1,2 @@
+import { SetMetadata } from '@nestjs/common';
+export const ExtendUser = () => SetMetadata('extendUser', true);
diff --git a/api/src/users/interceptors/users.interceptor.ts b/api/src/users/interceptors/users.interceptor.ts
index d0fd2785ebd82d8823a3163ad7e5f10a936ea00a..b1f6d8a8c03cc2c7e34f985bc6199226809b4d88 100644
--- a/api/src/users/interceptors/users.interceptor.ts
+++ b/api/src/users/interceptors/users.interceptor.ts
@@ -4,6 +4,7 @@ import {
   Injectable,
   NestInterceptor,
 } from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { Observable } from 'rxjs';
 import { User } from '../models/user.model';
@@ -11,7 +12,10 @@ import { UsersService } from '../users.service';
 
 @Injectable()
 export class UsersInterceptor implements NestInterceptor {
-  constructor(private readonly usersService: UsersService) {}
+  constructor(
+    private readonly usersService: UsersService,
+    private readonly reflector: Reflector,
+  ) {}
 
   async intercept(
     context: ExecutionContext,
@@ -19,8 +23,12 @@ export class UsersInterceptor implements NestInterceptor {
   ): Promise<Observable<any>> {
     const ctx = GqlExecutionContext.create(context);
     const req = ctx.getContext().req ?? ctx.switchToHttp().getRequest();
+    const extendUser = this.reflector.getAllAndOverride<boolean>('extendUser', [
+      context.getHandler(),
+      context.getClass(),
+    ]);
 
-    if (req.userExtended) return next.handle(); // user already extended
+    if (req.userExtended || !extendUser) return next.handle(); // user already extended
     req.userExtended = true;
 
     const user: User = req.user;
diff --git a/api/src/users/models/user.model.ts b/api/src/users/models/user.model.ts
index 980d7c1af5d7ca2a15cf997e316fd6ca51c29480..f611151c02a8f4d21f050e334d30656cf0b7886f 100644
--- a/api/src/users/models/user.model.ts
+++ b/api/src/users/models/user.model.ts
@@ -22,4 +22,7 @@ export class User {
   agreeNDA?: boolean;
 
   extraFields?: Record<string, any>;
+
+  @Column({ nullable: true })
+  refreshToken?: string;
 }
diff --git a/api/src/users/users.module.ts b/api/src/users/users.module.ts
index b5374872ed6f2336185bc5d10c5f9c680b473507..e052012a2c8312633da8d90c84b9705c5b243a8f 100644
--- a/api/src/users/users.module.ts
+++ b/api/src/users/users.module.ts
@@ -16,5 +16,6 @@ import { UsersService } from './users.service';
       useClass: UsersInterceptor,
     },
   ],
+  exports: [UsersService],
 })
 export class UsersModule {}
diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts
index d13ae670e9499d07b768cd3706ff1173f3de4135..7e8461d6a621fcb62a53191bd311c601de58c8bb 100644
--- a/api/src/users/users.resolver.ts
+++ b/api/src/users/users.resolver.ts
@@ -9,10 +9,12 @@ import { GlobalAuthGuard } from '../auth/guards/global-auth.guard';
 import { GQLRequest } from '../common/decorators/gql-request.decoractor';
 import { CurrentUser } from '../common/decorators/user.decorator';
 import EngineService from '../engine/engine.service';
+import { ExtendUser } from './decorators/extend-user.decorator';
 import { UpdateUserInput } from './inputs/update-user.input';
 import { User } from './models/user.model';
 import { UsersService } from './users.service';
 
+@ExtendUser()
 @UseGuards(GlobalAuthGuard)
 @Resolver()
 export class UsersResolver {
diff --git a/api/src/users/users.service.ts b/api/src/users/users.service.ts
index 3138f9a5e748bb329b154d9ac3e8f9319213066f..04b951092bef81489ddc08e28f8bb04f4e4d7abe 100644
--- a/api/src/users/users.service.ts
+++ b/api/src/users/users.service.ts
@@ -4,7 +4,10 @@ import { Repository } from 'typeorm';
 import { UpdateUserInput } from './inputs/update-user.input';
 import { User } from './models/user.model';
 
-export type InternalUser = Pick<User, 'id' | 'agreeNDA'>;
+export type InternalUser = Pick<User, 'id' | 'agreeNDA' | 'refreshToken'>;
+export type UserDataUpdate = Partial<
+  Pick<User, 'agreeNDA' | 'refreshToken'> | UpdateUserInput
+>;
 
 @Injectable()
 export class UsersService {
@@ -31,10 +34,10 @@ export class UsersService {
   /**
    * Update a user
    * @param {string} id - The id of the user to update.
-   * @param {UpdateUserInput} data - update params
+   * @param {UserDataUpdate} data - update params
    * @returns The updated user.
    */
-  async update(id: string, data: UpdateUserInput): Promise<InternalUser> {
+  async update(id: string, data: UserDataUpdate): Promise<InternalUser> {
     const updateData = {
       id,
       ...data,