diff --git a/Pipfile b/Pipfile
index 9a7f597ecb8e333c9e181141fd32d511045d8f96..d1a67df51cdde9c86c390ea0b8a8228a527f87f8 100644
--- a/Pipfile
+++ b/Pipfile
@@ -1,27 +1,27 @@
-[[source]]
-
-url = "https://pypi.python.org/simple"
-verify_ssl = true
-name = "pypi"
-
-
-[packages]
-
-flask = "*"
-flask-sqlalchemy = "*"
-pytest = "*"
-flask-restful = "*"
-marshmallow = "*"
-flask-jwt-extended = "*"
-marshmallow-enum = "*"
-storage = { git="git@gitlab.fi.muni.cz:grp-kontr2/kontr-storage-module.git", editable='true'  }
-gitpython = "*"
-
-
-[dev-packages]
-
-
-
-[requires]
-
-python_version = "3.6"
+[[source]]
+
+url = "https://pypi.python.org/simple"
+verify_ssl = true
+name = "pypi"
+
+
+[packages]
+
+flask = "*"
+flask-sqlalchemy = "*"
+pytest = "*"
+flask-restful = "*"
+marshmallow = "*"
+flask-jwt-extended = "*"
+marshmallow-enum = "*"
+storage = { git="git@gitlab.fi.muni.cz:grp-kontr2/kontr-storage-module.git", editable='true'  }
+gitpython = "*"
+
+
+[dev-packages]
+
+
+
+[requires]
+
+python_version = "3.6"
\ No newline at end of file
diff --git a/portal/__init__.py b/portal/__init__.py
index f677dbc03604b00e8d573d204b068afb492997ac..c1b11443a2d76a2948c83d2aee0da1f6daa4bca3 100644
--- a/portal/__init__.py
+++ b/portal/__init__.py
@@ -1,13 +1,14 @@
 from flask import Flask
+from flask_jwt_extended import JWTManager
+
 from portal.config import CONFIGURATIONS
 from flask_sqlalchemy import SQLAlchemy
 
 import os
 from storage import Storage
 
-
 db = SQLAlchemy()
-
+jwt = JWTManager()
 # TODO - urgent
 storage = Storage({
     "submissions_dir": "todo",
@@ -32,11 +33,8 @@ def create_app():
     config_app(app)
     # database bind to app
     db.init_app(app)
-
+    # init the jwt
+    jwt.init_app(app)
     return app
 
-
-# app = create_app()
-
 import portal.database.models
-
diff --git a/portal/database/models.py b/portal/database/models.py
index 1424d00dc5c931ad8c6b6d1b833e9c557aad3bfd..49729eeebb59ec9cd5cb3b2c416c074556ff5448 100644
--- a/portal/database/models.py
+++ b/portal/database/models.py
@@ -4,11 +4,14 @@ import datetime
 
 from sqlalchemy import event
 from sqlalchemy.ext.hybrid import hybrid_property
+from werkzeug.security import generate_password_hash, check_password_hash
 
 from portal import db
 from portal.database.mixins import EntityBase
 from portal.database.exceptions import PortalDbError
 from portal.tools import time
+
+
 # uuid as primary key source:
 # https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid
 
@@ -29,12 +32,30 @@ class User(db.Model, EntityBase):
     username = db.Column(db.String(15), unique=True, nullable=False)
     name = db.Column(db.String(50))
     is_admin = db.Column(db.Boolean, default=False)
-    password_hash = db.Column(db.String(50), default=None)  # optional - after password change
+    password_hash = db.Column(db.String(60), default=None)  # optional - after password change
 
     submissions = db.relationship("Submission", back_populates="user", cascade="all, delete-orphan",
                                   passive_deletes=True)
     review_items = db.relationship("ReviewItem", back_populates="user")
 
+    def set_password(self, password: str):
+        """Sets password for the user
+
+        Args:
+            password(str): Unhashed password
+        """
+        self.password_hash = generate_password_hash(password)
+
+    def verify_password(self, password: str) -> bool:
+        """
+        Args:
+            password(str): Unhashed password
+
+        Returns(bool): True if the password is valid, False otherwise
+
+        """
+        return check_password_hash(self.password_hash, password)
+
     @property
     def courses(self):
         result = []
@@ -65,9 +86,12 @@ class Course(db.Model, EntityBase):
 
     # Info on cascades: http://docs.sqlalchemy.org/en/latest/orm/cascades.html
     # Cascades here simulate a composition - roles, groups and projects exist only when associated to a course
-    roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
-    groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
-    projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
+    roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan",
+                            passive_deletes=True)
+    groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan",
+                             passive_deletes=True)
+    projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan",
+                               passive_deletes=True)
 
     def __init__(self, name, codename) -> None:
         self.name = name
@@ -96,7 +120,8 @@ class Project(db.Model, EntityBase):
 
     course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False)
     course = db.relationship("Course", back_populates="projects", uselist=False)
-    submissions = db.relationship("Submission", back_populates="project", cascade="all, delete-orphan",
+    submissions = db.relationship("Submission", back_populates="project",
+                                  cascade="all, delete-orphan",
                                   passive_deletes=True)
 
     __table_args__ = (
@@ -113,9 +138,10 @@ class Project(db.Model, EntityBase):
 
     def set_config(self, **kwargs):
         for k, w in kwargs.items():
-            if k in ('test_files_source', 'file_whitelist', 'pre_submit_script', 'post_submit_script',
-                     'submission_parameters', 'submission_scheduler_config', 'submissions_allowed_from',
-                     'submissions_allowed_to', 'archive_from'):
+            if k in (
+            'test_files_source', 'file_whitelist', 'pre_submit_script', 'post_submit_script',
+            'submission_parameters', 'submission_scheduler_config', 'submissions_allowed_from',
+            'submissions_allowed_to', 'archive_from'):
                 setattr(self.config, k, w)
 
     def __init__(self, course, name, test_files_source) -> None:
@@ -203,7 +229,8 @@ class Role(db.Model, EntityBase):
     course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False)
     course = db.relationship("Course", back_populates="roles", uselist=False)
 
-    permissions = db.relationship("RolePermissions", back_populates='role', cascade="all, delete-orphan",
+    permissions = db.relationship("RolePermissions", back_populates='role',
+                                  cascade="all, delete-orphan",
                                   passive_deletes=True, lazy='joined', uselist=False)
 
     def set_permissions(self, **kwargs):
@@ -357,7 +384,8 @@ class ReviewItem(db.Model, EntityBase):
     review_id = db.Column(db.Integer, db.ForeignKey('review.id'), nullable=False)
     review = db.relationship("Review", uselist=False, back_populates="review_items")
     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
-    user = db.relationship("User", uselist=False, back_populates="review_items")  # the review item's author
+    user = db.relationship("User", uselist=False,
+                           back_populates="review_items")  # the review item's author
     content = db.Column(db.Text)
     file = db.Column(db.String(100), nullable=False)
     line = db.Column(db.Integer, nullable=False)
diff --git a/portal/rest/auth/login.py b/portal/rest/auth/login.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e981d0d55da8cc79f8c90cd05ad76edc61072ff4 100644
--- a/portal/rest/auth/login.py
+++ b/portal/rest/auth/login.py
@@ -0,0 +1,61 @@
+from flask_jwt_extended import create_access_token, create_refresh_token, \
+    jwt_refresh_token_required, get_jwt_identity, jwt_required
+from flask_restful import Resource, Api
+from flask import Blueprint, request, jsonify
+
+from portal.service.auth import login_user
+from portal.service.errors import UnauthorizedError
+
+auth = Blueprint('courses', __name__, url_prefix='/auth')
+auth_api = Api(auth)
+
+
+class Login(Resource):
+    def post(self):
+        username = request.json.get('username', None)
+        password = request.json.get('password', None)
+        gitlab_access_token = request.json.get('gitlab_access_token', None)
+        user = login_user(gitlab_access_token, password, username)
+
+        ret = {
+            'access_token': create_access_token(identity=user.id),
+            'refresh_token': create_refresh_token(identity=user.id)
+        }
+
+        return jsonify(ret), 200
+
+
+class Refresh(Resource):
+    @jwt_refresh_token_required
+    def post(self):
+        current_user = get_jwt_identity()
+        if not current_user:
+            raise UnauthorizedError()
+
+        ret = {
+            'access_token': create_access_token(identity=current_user)
+        }
+
+        return jsonify(ret), 200
+
+
+class Logout(Resource):
+    @jwt_required
+    def post(self):
+        # TODO
+        current_user = get_jwt_identity()
+
+        if not current_user:
+            raise UnauthorizedError()
+
+        ret = {
+            'access_token': None,
+            'refresh_token': None
+        }
+
+        return jsonify(ret), 200
+
+
+auth_api.add_resource(Login, '/login')
+auth_api.add_resource(Refresh, '/refresh')
+auth_api.add_resource(Logout, '/logout')
diff --git a/portal/rest/courses/courses.py b/portal/rest/courses/courses.py
index 70e3741586fd89aff19a203db2154f1586f32847..979eab41c8185c2cf77d67560dc3de968a54c371 100644
--- a/portal/rest/courses/courses.py
+++ b/portal/rest/courses/courses.py
@@ -22,16 +22,12 @@ class CourseResource(Resource):
     def get(self, cid):
         log.info(f"GET to {request.url}")
         course = service.find_course(cid)
-        if not course:
-            abort(404, message=f"Could not find course {cid}.")
         return course_schema.dump(course)
 
     @error_handler
     def delete(self, cid):
         log.info(f"DELETE to {request.url}")
         course = service.find_course(cid)
-        if not course:
-            abort(404, message=f"Could not find course {cid}.")
         service.delete_entity(course)
         return '', 204
 
@@ -39,7 +35,6 @@ class CourseResource(Resource):
     def put(self, cid):
         log.info(f"PUT to {request.url}")
         course = service.find_course(cid)
-
         json_data = request.get_json()
         if not json_data:
             abort(400, message='No data provided for course update.')
diff --git a/portal/service/auth.py b/portal/service/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..a57cc398419a6084201002ef3654a3bb39f6e111
--- /dev/null
+++ b/portal/service/auth.py
@@ -0,0 +1,36 @@
+from portal.service.errors import IncorrectPasswordError
+from portal.service.service import find_user
+
+
+def login_user(gitlab_access_token=None, password=None, username=None):
+    if gitlab_access_token:
+        return auth_gitlab_access_token(
+            username=username,
+            gitlab_access_token=gitlab_access_token
+        )
+
+    return auth_username_password(username=username, password=password)
+
+
+def auth_gitlab_access_token(username, gitlab_access_token):
+    """Authentication using gitlab token
+
+    Steps:
+        Send token to gitlab
+        Verify that token is for user
+
+    Args:
+        gitlab_access_token:
+
+    Returns:
+
+    """
+
+
+def auth_username_password(username, password):
+    user = find_user(username)
+
+    if user.verify_password(password=password):
+        return user
+
+    raise IncorrectPasswordError()
diff --git a/portal/service/errors.py b/portal/service/errors.py
index e813436149d07a8e814da22217659bbcd7b5c9f7..b0170110de0a37ceca9a7bbaa7d8608d54ef7570 100644
--- a/portal/service/errors.py
+++ b/portal/service/errors.py
@@ -61,3 +61,10 @@ class ForbiddenError(PortalAPIError):
             message['note'] = note
 
         super().__init__(code=401, message=message)
+
+
+class IncorrectPasswordError(UnauthorizedError):
+    def __init__(self):
+        super().__init__(note="Password is incorrect")
+
+
diff --git a/sample_data/data_init.py b/sample_data/data_init.py
index 89aca8c6b8e642c2a9322ef387addcdca3b6446b..cc407509c7e010d47c17712619fb6978c010400f 100644
--- a/sample_data/data_init.py
+++ b/sample_data/data_init.py
@@ -1,4 +1,4 @@
-from portal.database.models import User, Course, Project, Group, Role, Submission, Review
+from portal.database.models import User, Course, Project, Group, Role, Submission
 
 
 def init_data(app, db):
@@ -13,8 +13,10 @@ def init_data(app, db):
 
 
 def create_users(db):
-    db.session.add(User(uco=445, email="foo@foo.cz", username="xfoo"))
-    db.session.add(User(uco=123, email="bar@bar.com", username="xbar"))
+    foo_user = User(uco=445, email="foo@bar.cz", username="xfoo")
+    foo_user.set_password('123456')
+    db.session.add(foo_user)
+    db.session.add(User(uco=123, email="bar@baz.cz", username="xbar"))
     db.session.commit()
 
 
diff --git a/tests/rest/test_user_rest.py b/tests/rest/test_user_rest.py
index e78d0b1973c8d4c4ffe914f42e0f93b4ccc984c9..fb2041a65a276548045b71e81e6ffb869d26712e 100644
--- a/tests/rest/test_user_rest.py
+++ b/tests/rest/test_user_rest.py
@@ -39,8 +39,6 @@ import json
 	- reviews associated with the user's submissions: accessible through the user's submissions
 
 '''
-user_schema = UserSchema()
-users_schema = UserSchema(many=True, only=('id', 'uco', 'username', 'email'))
 
 
 def assert_user(expected: User, actual: dict):
@@ -88,7 +86,6 @@ def test_create(client):
     assert_user_in(db_users, resp_new_user)
 
 
-#follow_redirects=True
 def test_list(client):
     response = client.get('/users/')
     assert response.status_code == 200
@@ -107,7 +104,6 @@ def test_read(client):
     assert response.mimetype == 'application/json'
 
     data = json.loads(str(response.get_data().decode("utf-8")))
-    print(f"-----------{data}--------------------")
     assert_user(user, data)