reintegration authentication patterns
diff --git a/logs/audit.log b/logs/audit.log
deleted file mode 100644
index 591c4a4..0000000
--- a/logs/audit.log
+++ /dev/null
@@ -1,48 +0,0 @@
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
-SERVICE AUDIT : 
-MichaelHanl@Tue Aug 11 15:50:35 CEST 2015
-Status 105; Args None; 
diff --git a/logs/default_audit.log b/logs/default_audit.log
deleted file mode 100644
index e69de29..0000000
--- a/logs/default_audit.log
+++ /dev/null
diff --git a/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java b/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
new file mode 100644
index 0000000..be9e9ce
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
@@ -0,0 +1,170 @@
+package de.ids_mannheim.korap.config;
+
+import com.nimbusds.jose.*;
+import com.nimbusds.jose.crypto.MACSigner;
+import com.nimbusds.jose.crypto.MACVerifier;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.joda.time.DateTime;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 19/05/2014
+ */
+public class JWTSigner {
+
+    private URL issuer;
+    private JWSSigner signer;
+    private JWSVerifier verifier;
+    private final int defaultttl;
+
+    public JWTSigner(final byte[] secret, URL issuer, final int defaultttl) {
+        this.issuer = issuer;
+        this.signer = new MACSigner(secret);
+        this.verifier = new MACVerifier(secret);
+        this.defaultttl = defaultttl;
+    }
+
+    public JWTSigner(final byte[] secret, String issuer)
+            throws MalformedURLException {
+        this(secret, new URL(issuer), 72 * 60 * 60);
+    }
+
+    public SignedJWT createJWT(User user, Map<String, Object> attr) {
+        return signContent(user, attr, defaultttl);
+    }
+
+    public SignedJWT signContent(User user, Map<String, Object> attr, int ttl) {
+        String scopes;
+
+        JWTClaimsSet cs = new JWTClaimsSet();
+        cs.setIssuerClaim(this.issuer.toString());
+
+        if ((scopes = (String) attr.get(Attributes.SCOPES)) != null) {
+            Map<String, Object> claims = Scopes
+                    .mapOpenIDConnectScopes(scopes, user.getDetails());
+            cs.setCustomClaims(claims);
+            cs.setCustomClaim(Attributes.SCOPES,
+                    ((String) attr.get(Attributes.SCOPES)).toLowerCase());
+        }
+
+        cs.setSubjectClaim(user.getUsername());
+        if (attr.get(Attributes.CLIENT_ID) != null)
+            cs.setAudienceClaim(
+                    new String[] { (String) attr.get(Attributes.CLIENT_ID) });
+        cs.setExpirationTimeClaim(
+                TimeUtils.getNow().plusSeconds(ttl).getMillis());
+        SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),
+                cs);
+        try {
+            signedJWT.sign(signer);
+        }catch (JOSEException e) {
+            return null;
+        }
+        return signedJWT;
+    }
+
+    /**
+     * @param username
+     * @param json
+     * @return
+     */
+    public SignedJWT signContent(String username, String userclient,
+            String json, int ttl) {
+        JWTClaimsSet cs = new JWTClaimsSet();
+        cs.setSubjectClaim(username);
+        if (!json.isEmpty())
+            cs.setCustomClaim("data", json);
+        cs.setExpirationTimeClaim(
+                TimeUtils.getNow().plusSeconds(ttl).getMillis());
+        cs.setIssuerClaim(this.issuer.toString());
+
+        if (!userclient.isEmpty())
+            cs.setCustomClaim("userip", userclient);
+
+        SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256),
+                cs);
+        try {
+            signedJWT.sign(signer);
+        }catch (JOSEException e) {
+            return null;
+        }
+        return signedJWT;
+    }
+
+    public SignedJWT signContent(String username, String userclient,
+            String json) {
+        return signContent(username, userclient, json, defaultttl);
+    }
+
+    public SignedJWT createSignedToken(String username) {
+        return createSignedToken(username, defaultttl);
+    }
+
+    // add client info
+    public SignedJWT createSignedToken(String username, int ttl) {
+        return signContent(username, "", "", ttl);
+    }
+
+    private SignedJWT verifyToken(String token) throws KustvaktException {
+        SignedJWT client;
+        try {
+            client = SignedJWT.parse(token);
+            if (!client.verify(verifier))
+                throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+
+            if (!new DateTime(client.getJWTClaimsSet().getExpirationTimeClaim())
+                    .isAfterNow())
+                throw new KustvaktException(StatusCodes.EXPIRED,
+                        "authentication token is expired", token);
+        }catch (ParseException | JOSEException e) {
+            //todo: message or entity, how to treat??!
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+                    "token could not be verified", token);
+        }
+        return client;
+    }
+
+    // does not care about expiration times
+    public String retrieveContent(String signedContent) throws
+            KustvaktException {
+        SignedJWT jwt;
+        try {
+            jwt = SignedJWT.parse(signedContent);
+            if (!jwt.verify(verifier))
+                throw new KustvaktException(StatusCodes.REQUEST_INVALID,
+                        "token invalid", signedContent);
+            return (String) jwt.getJWTClaimsSet().getCustomClaim("data");
+        }catch (ParseException | JOSEException e) {
+            return null;
+        }
+    }
+
+    public TokenContext getTokenContext(String idtoken)
+            throws ParseException, JOSEException, KustvaktException {
+        SignedJWT signedJWT = verifyToken(idtoken);
+
+        TokenContext c = new TokenContext(
+                signedJWT.getJWTClaimsSet().getSubjectClaim());
+        if (signedJWT.getJWTClaimsSet().getAudienceClaim() != null)
+            c.addContextParameter(Attributes.CLIENT_ID,
+                    signedJWT.getJWTClaimsSet().getAudienceClaim()[0]);
+        c.setExpirationTime(
+                signedJWT.getJWTClaimsSet().getExpirationTimeClaim());
+        c.setToken(idtoken);
+        c.setParameters(signedJWT.getJWTClaimsSet().getCustomClaims());
+        return c;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/config/URIParam.java b/src/main/java/de/ids_mannheim/korap/config/URIParam.java
new file mode 100644
index 0000000..3ab3724
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/URIParam.java
@@ -0,0 +1,26 @@
+package de.ids_mannheim.korap.config;
+
+import lombok.Getter;
+
+/**
+ * @author hanl
+ * @date 15/07/15
+ */
+@Getter
+public class URIParam extends ParamFields.Param {
+
+    private final String uriFragment;
+    private final Long uriExpiration;
+
+    public URIParam(String uri, Long expire) {
+        this.uriFragment = uri;
+        this.uriExpiration = expire;
+    }
+
+    @Override
+    public boolean hasValues() {
+        return this.uriFragment != null && !this.uriFragment.isEmpty()
+                && this.uriExpiration != null;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/APIAuthentication.java b/src/main/java/de/ids_mannheim/korap/security/auth/APIAuthentication.java
new file mode 100644
index 0000000..df9f1b8
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/APIAuthentication.java
@@ -0,0 +1,86 @@
+package de.ids_mannheim.korap.security.auth;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jwt.SignedJWT;
+import de.ids_mannheim.korap.config.JWTSigner;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationIface;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.StringUtils;
+import lombok.AccessLevel;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+import java.text.ParseException;
+import java.util.Map;
+
+/**
+ * Created by hanl on 5/23/14.
+ */
+public class APIAuthentication implements AuthenticationIface {
+
+    private JWTSigner signedToken;
+
+    public APIAuthentication(KustvaktConfiguration bconfig) {
+        KustvaktConfiguration config = bconfig;
+        this.signedToken = new JWTSigner(config.getSharedSecret(),
+                config.getIssuer(), config.getTokenTTL());
+    }
+
+    @Cacheable(value = "id_tokens", key = "#authToken")
+    @Override
+    public TokenContext getUserStatus(String authToken)
+            throws KustvaktException {
+        try {
+            authToken = StringUtils.stripTokenType(authToken);
+            TokenContext c = signedToken.getTokenContext(authToken);
+            c.setTokenType(Attributes.API_AUTHENTICATION);
+            return c;
+        }catch (JOSEException | ParseException e) {
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+        }
+    }
+
+    @Override
+    public TokenContext createUserSession(User user, Map<String, Object> attr)
+            throws KustvaktException {
+        TokenContext c = new TokenContext(user.getUsername());
+        SignedJWT jwt = signedToken.createJWT(user, attr);
+        try {
+            c.setExpirationTime(jwt.getJWTClaimsSet().getExpirationTimeClaim());
+        }catch (ParseException e) {
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+        }
+        c.setTokenType(Attributes.API_AUTHENTICATION);
+        c.setToken(jwt.serialize());
+        CacheManager.getInstance().getCache("id_tokens")
+                .put(new Element(c.getToken(), c));
+
+        return c;
+    }
+
+    // todo: cache and set expiration to token expiration. if token in that cache, it is not to be used anymore!
+    @CacheEvict(value = "id_tokens", key = "#token")
+    @Override
+    public void removeUserSession(String token) throws KustvaktException {
+        // invalidate token!
+    }
+
+    @Override
+    public TokenContext refresh(TokenContext context) throws KustvaktException {
+        return null;
+    }
+
+
+    @Override
+    public String getIdentifier() {
+        return Attributes.API_AUTHENTICATION;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/BasicHttpAuth.java b/src/main/java/de/ids_mannheim/korap/security/auth/BasicHttpAuth.java
new file mode 100644
index 0000000..07e8ace
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/BasicHttpAuth.java
@@ -0,0 +1,75 @@
+package de.ids_mannheim.korap.security.auth;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.interfaces.AuthenticationIface;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.StringUtils;
+import org.apache.commons.codec.binary.Base64;
+
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 28/04/2015
+ */
+public class BasicHttpAuth implements AuthenticationIface {
+
+    public static String[] decode(String token) {
+        if (StringUtils.getTokenType(token)
+                .equals(Attributes.BASIC_AUTHENTICATION)) {
+            token = StringUtils.stripTokenType(token);
+            String[] sp = new String(Base64.decodeBase64(token)).split(":", 2);
+            sp[0].replaceAll(" ", "");
+            sp[1].replaceAll(" ", "");
+            return sp;
+        }
+        return null;
+    }
+
+    public static String encode(String user, String pass) {
+        String s = user + ":" + pass;
+        return new String(Base64.encodeBase64(s.getBytes()));
+    }
+
+    @Override
+    public TokenContext getUserStatus(String authToken) throws
+            KustvaktException {
+        authToken = StringUtils.stripTokenType(authToken);
+        String[] values = decode(authToken);
+        if (values != null) {
+            TokenContext c = new TokenContext(values[0]);
+            c.setTokenType(Attributes.BASIC_AUTHENTICATION);
+            c.setSecureRequired(true);
+            c.setToken(authToken);
+            //            fixme: you can make queries, but user sensitive data is off limits?!
+            //            c.addContextParameter(Attributes.SCOPES,
+            //                    Scopes.Scope.search.toString());
+            return c;
+        }
+        return null;
+    }
+
+    // not supported!
+    @Override
+    public TokenContext createUserSession(User user, Map<String, Object> attr)
+            throws KustvaktException {
+        return null;
+    }
+
+    @Override
+    public void removeUserSession(String token) throws KustvaktException {
+    }
+
+    @Override
+    public TokenContext refresh(TokenContext context) throws KustvaktException {
+        return null;
+    }
+
+
+    @Override
+    public String getIdentifier() {
+        return Attributes.BASIC_AUTHENTICATION;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java b/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java
new file mode 100644
index 0000000..a703e39
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java
@@ -0,0 +1,691 @@
+package de.ids_mannheim.korap.security.auth;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.URIParam;
+import de.ids_mannheim.korap.exceptions.*;
+import de.ids_mannheim.korap.interfaces.*;
+import de.ids_mannheim.korap.user.*;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.springframework.cache.annotation.CachePut;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * contains the logic to authentication and registration processes. Uses
+ * interface implementations (AuthenticationIface) for different databases and handlers
+ *
+ * @author hanl
+ */
+public class KustvaktAuthenticationManager extends AuthenticationManagerIface {
+
+    private static String KEY = "kustvakt:key";
+    private static Logger jlog = KustvaktLogger
+            .initiate(KustvaktAuthenticationManager.class);
+    private EncryptionIface crypto;
+    private EntityHandlerIface entHandler;
+    private AuditingIface auditing;
+    private final LoginCounter counter;
+    private Cache user_cache;
+
+    public KustvaktAuthenticationManager(EntityHandlerIface userdb,
+            EncryptionIface crypto, KustvaktConfiguration config,
+            AuditingIface auditer) {
+        this.entHandler = userdb;
+        this.crypto = crypto;
+        this.auditing = auditer;
+        this.counter = new LoginCounter(config);
+        this.user_cache = CacheManager.getInstance().getCache("users");
+    }
+
+    /**
+     * get session object if token was a session token
+     *
+     * @param token
+     * @param host
+     * @param useragent
+     * @return
+     * @throws KustvaktException
+     */
+    public TokenContext getTokenStatus(String token, String host,
+            String useragent) throws KustvaktException {
+        jlog.info("getting session status of token '{}'", token);
+        AuthenticationIface provider = getProvider(
+                StringUtils.getTokenType(token));
+        TokenContext context = provider.getUserStatus(token);
+        if (!matchStatus(host, useragent, context))
+            provider.removeUserSession(token);
+        return context;
+    }
+
+    public User getUser(String username) throws KustvaktException {
+        User user;
+        String key = cache_key(username);
+        Element e = user_cache.get(key);
+
+        if (e != null) {
+            Map map = (Map) e.getObjectValue();
+            user = User.UserFactory.toUser(map);
+        }else {
+            try {
+                user = entHandler.getAccount(username);
+                user_cache.put(new Element(key, user.toCache()));
+                // todo: not valid. for the duration of the session, the host should not change!
+            }catch (EmptyResultException e1) {
+                // do nothing
+                return null;
+            }
+        }
+        //todo:
+        //        user.addField(Attributes.HOST, context.getHostAddress());
+        //        user.addField(Attributes.USER_AGENT, context.getUserAgent());
+        return user;
+    }
+
+    public TokenContext refresh(TokenContext context) throws KustvaktException {
+        AuthenticationIface provider = getProvider(context.getTokenType());
+        try {
+            provider.removeUserSession(context.getToken());
+            User user = getUser(context.getUsername());
+            return provider.createUserSession(user, context.getParameters());
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.LOGIN_FAILED);
+        }
+    }
+
+    /**
+     * @param type
+     * @param attributes contains username and password to authenticate the user.
+     *                   Depending of the authentication schema, may contain other values as well
+     * @return User
+     * @throws KustvaktException
+     */
+
+    public User authenticate(int type, String username, String password,
+            Map<String, Object> attributes) throws KustvaktException {
+        User user;
+        switch (type) {
+            case 1:
+                // todo:
+                user = authenticateShib(attributes);
+                break;
+            default:
+                user = authenticate(username, password, attributes);
+                break;
+        }
+        auditing.audit(AuditRecord
+                .serviceRecord(user.getId(), StatusCodes.LOGIN_SUCCESSFUL,
+                        user.toString()));
+        return user;
+    }
+
+    @CachePut(value = "users", key = "#user.getUsername()")
+    public TokenContext createTokenContext(User user, Map<String, Object> attr,
+            String provider_key) throws KustvaktException {
+        AuthenticationIface provider = getProvider(provider_key);
+
+        if (attr.get(Attributes.SCOPES) != null)
+            this.getUserDetails(user);
+
+        TokenContext context = provider.createUserSession(user, attr);
+        if (context == null)
+            throw new KustvaktException(StatusCodes.NOT_SUPPORTED);
+        context.setUserAgent((String) attr.get(Attributes.USER_AGENT));
+        context.setHostAddress(Attributes.HOST);
+        return context;
+    }
+
+    //todo: test
+    private boolean matchStatus(String host, String useragent,
+            TokenContext context) {
+        if (host.equals(context.getHostAddress())) {
+            if (useragent.equals(context.getUserAgent()))
+                return true;
+        }
+        return false;
+    }
+
+    private User authenticateShib(Map<String, Object> attributes)
+            throws KustvaktException {
+        // todo use persistent id, since eppn is not unique
+        String eppn = (String) attributes.get(Attributes.EPPN);
+
+        if (eppn == null || eppn.isEmpty())
+            throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+
+        if (!attributes.containsKey(Attributes.EMAIL)
+                && crypto.validateEmail(eppn) != null)
+            attributes.put(Attributes.EMAIL, eppn);
+
+        // fixme?!
+        User user = isRegistered(eppn);
+        if (user == null)
+            user = createShibbUserAccount(attributes);
+        return user;
+    }
+
+    //todo: what if attributes null?
+    private User authenticate(String username, String password,
+            Map<String, Object> attr) throws KustvaktException {
+        Map<String, Object> attributes = crypto.validateMap(attr);
+        String uPassword, safeUS;
+        User unknown;
+        // just to make sure that the plain password does not appear anywhere in the logs!
+
+        try {
+            safeUS = crypto.validateString(username);
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.LOGIN_FAILED, username);
+        }
+
+        if (safeUS == null || safeUS.isEmpty())
+            throw new WrappedException(new KustvaktException(username,
+                    StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED);
+        else {
+            try {
+                unknown = entHandler.getAccount(safeUS);
+                unknown.setSettings(
+                        entHandler.getUserSettings(unknown.getId()));
+            }catch (EmptyResultException e) {
+                // mask exception to disable user guessing in possible attacks
+                throw new WrappedException(new KustvaktException(username,
+                        StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED,
+                        username);
+            }catch (KustvaktException e) {
+                throw new WrappedException(e, StatusCodes.LOGIN_FAILED,
+                        attributes.toString());
+            }
+        }
+        jlog.trace("Authentication: found user under name " + unknown
+                .getUsername());
+        if (unknown instanceof KorAPUser) {
+            if (password == null || password.isEmpty())
+                throw new WrappedException(
+                        new KustvaktException(unknown.getId(),
+                                StatusCodes.BAD_CREDENTIALS),
+                        StatusCodes.LOGIN_FAILED, username);
+
+            KorAPUser user = (KorAPUser) unknown;
+            boolean check = crypto.checkHash(password, user.getPassword());
+
+            if (!check) {
+                // the fail counter only applies for wrong password
+                jlog.warn("Wrong Password!");
+                processLoginFail(unknown);
+                throw new WrappedException(new KustvaktException(user.getId(),
+                        StatusCodes.BAD_CREDENTIALS), StatusCodes.LOGIN_FAILED,
+                        username);
+            }
+
+            // bad credentials error has presedence over account locked or unconfirmed codes
+            // since latter can lead to account guessing of third parties
+            if (user.isAccountLocked()) {
+                URIParam param = (URIParam) user.getField(URIParam.class);
+
+                if (param.hasValues()) {
+                    jlog.debug("Account is not yet activated for user '{}'",
+                            user.getUsername());
+                    if (TimeUtils.getNow().isAfter(param.getUriExpiration())) {
+                        KustvaktLogger.ERROR_LOGGER
+                                .error("URI token is expired. Deleting account for user {}",
+                                        user.getUsername());
+                        deleteAccount(user);
+                        throw new WrappedException(
+                                new KustvaktException(unknown.getId(),
+                                        StatusCodes.EXPIRED,
+                                        "account confirmation uri has expired",
+                                        param.getUriFragment()),
+                                StatusCodes.LOGIN_FAILED, username);
+                    }
+                    throw new WrappedException(
+                            new KustvaktException(unknown.getId(),
+                                    StatusCodes.UNCONFIRMED_ACCOUNT),
+                            StatusCodes.LOGIN_FAILED, username);
+                }
+                KustvaktLogger.ERROR_LOGGER
+                        .error("ACCESS DENIED: account not active for '{}'",
+                                unknown.getUsername());
+                throw new WrappedException(
+                        new KustvaktException(unknown.getId(),
+                                StatusCodes.ACCOUNT_DEACTIVATED),
+                        StatusCodes.LOGIN_FAILED, username);
+            }
+
+        }else if (unknown instanceof ShibUser) {
+            //todo
+        }
+        jlog.debug("Authentication done: " + safeUS);
+        return unknown;
+    }
+
+    public User isRegistered(String username) throws KustvaktException {
+        User user;
+        if (username == null || username.isEmpty())
+            throw new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT,
+                    "username must be set", username);
+
+        try {
+            user = entHandler.getAccount(username);
+        }catch (EmptyResultException e) {
+            jlog.debug("user does not exist ({})", username);
+            return null;
+
+        }catch (KustvaktException e) {
+            KustvaktLogger.ERROR_LOGGER.error("KorAPException", e);
+            throw new KustvaktException(username, StatusCodes.ILLEGAL_ARGUMENT,
+                    "username invalid", username);
+        }
+        return user;
+    }
+
+    public void logout(TokenContext context) throws KustvaktException {
+        String key = cache_key(context.getUsername());
+        try {
+            AuthenticationIface provider = getProvider(context.getTokenType());
+            provider.removeUserSession(context.getToken());
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.LOGOUT_FAILED,
+                    context.toString());
+        }
+        auditing.audit(AuditRecord.serviceRecord(context.getUsername(),
+                StatusCodes.LOGOUT_SUCCESSFUL, context.toString()));
+        user_cache.remove(key);
+    }
+
+    private void processLoginFail(User user) throws KustvaktException {
+        counter.registerFail(user.getUsername());
+        if (!counter.validate(user.getUsername())) {
+            try {
+                this.lockAccount(user);
+            }catch (KustvaktException e) {
+                KustvaktLogger.ERROR_LOGGER
+                        .error("user account could not be locked!", e);
+                throw new WrappedException(e,
+                        StatusCodes.UPDATE_ACCOUNT_FAILED);
+            }
+            throw new WrappedException(new KustvaktException(user.getId(),
+                    StatusCodes.ACCOUNT_DEACTIVATED), StatusCodes.LOGIN_FAILED);
+        }
+    }
+
+    public void lockAccount(User user) throws KustvaktException {
+        if (!(user instanceof KorAPUser))
+            throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+
+        KorAPUser u = (KorAPUser) user;
+        u.setAccountLocked(true);
+        jlog.info("locking account for user: {}", user.getUsername());
+        entHandler.updateAccount(u);
+    }
+
+    public KorAPUser checkPasswordAllowance(KorAPUser user, String oldPassword,
+            String newPassword) throws KustvaktException {
+        String dbPassword = user.getPassword();
+
+        if (oldPassword.trim().equals(newPassword.trim())) {
+            // TODO: special error StatusCodes for this?
+            throw new WrappedException(new KustvaktException(user.getId(),
+                    StatusCodes.ILLEGAL_ARGUMENT),
+                    StatusCodes.PASSWORD_RESET_FAILED, newPassword);
+        }
+
+        boolean check = crypto.checkHash(oldPassword, dbPassword);
+
+        if (!check)
+            throw new WrappedException(new KustvaktException(user.getId(),
+                    StatusCodes.BAD_CREDENTIALS),
+                    StatusCodes.PASSWORD_RESET_FAILED);
+
+        try {
+            user.setPassword(crypto.produceSecureHash(newPassword));
+        }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+            //            throw new KorAPException(StatusCodes.ILLEGAL_ARGUMENT,
+            //                    "Creating password hash failed!", "password");
+            throw new WrappedException(new KustvaktException(user.getId(),
+                    StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
+                    newPassword), StatusCodes.PASSWORD_RESET_FAILED,
+                    user.toString(), newPassword);
+        }
+        return user;
+    }
+
+    //fixme: use clientinfo for logging/auditing?! = from where did he access the reset function?
+    @Override
+    public void resetPassword(String uriFragment, String username,
+            String newPassphrase) throws KustvaktException {
+        String safeUser, safePass;
+
+        try {
+            safeUser = crypto.validateString(username);
+            safePass = crypto.validatePassphrase(newPassphrase);
+        }catch (KustvaktException e) {
+            KustvaktLogger.ERROR_LOGGER.error("Error", e);
+            throw new WrappedException(new KustvaktException(username,
+                    StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
+                    newPassphrase), StatusCodes.PASSWORD_RESET_FAILED, username,
+                    newPassphrase);
+        }
+
+        try {
+            safePass = crypto.produceSecureHash(safePass);
+        }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+            KustvaktLogger.ERROR_LOGGER.error("Encoding/Algorithm Error", e);
+            throw new WrappedException(new KustvaktException(username,
+                    StatusCodes.ILLEGAL_ARGUMENT, "password invalid",
+                    newPassphrase), StatusCodes.PASSWORD_RESET_FAILED, username,
+                    uriFragment, newPassphrase);
+        }
+        int result = entHandler
+                .resetPassphrase(safeUser, uriFragment, safePass);
+
+        if (result == 0)
+            throw new WrappedException(
+                    new KustvaktException(username, StatusCodes.EXPIRED,
+                            "URI fragment expired", uriFragment),
+                    StatusCodes.PASSWORD_RESET_FAILED, username, uriFragment);
+        else if (result == 1)
+            jlog.info("successfully reset password for user {}", safeUser);
+    }
+
+    public void confirmRegistration(String uriFragment, String username)
+            throws KustvaktException {
+        String safeUser;
+        try {
+            safeUser = crypto.validateString(username);
+        }catch (KustvaktException e) {
+            KustvaktLogger.ERROR_LOGGER.error("error", e);
+            throw new WrappedException(e,
+                    StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
+                    uriFragment);
+        }
+        int r = entHandler.activateAccount(safeUser, uriFragment);
+        if (r == 0) {
+            User user;
+            try {
+                user = entHandler.getAccount(username);
+            }catch (EmptyResultException e) {
+                throw new WrappedException(new KustvaktException(username,
+                        StatusCodes.BAD_CREDENTIALS),
+                        StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
+                        uriFragment);
+            }
+            entHandler.deleteAccount(user.getId());
+            throw new WrappedException(
+                    new KustvaktException(user.getId(), StatusCodes.EXPIRED),
+                    StatusCodes.ACCOUNT_CONFIRMATION_FAILED, username,
+                    uriFragment);
+        }else if (r == 1)
+            jlog.info("successfully confirmed user registration for user {}",
+                    safeUser);
+        // register successful audit!
+    }
+
+    /**
+     * @param attributes
+     * @return
+     * @throws KustvaktException
+     */
+    //fixme: remove clientinfo object (not needed), use json representation to get stuff
+    public User createUserAccount(Map<String, Object> attributes)
+            throws KustvaktException {
+        Map<String, Object> safeMap = crypto.validateMap(attributes);
+        if (safeMap.get(Attributes.USERNAME) == null || ((String) safeMap
+                .get(Attributes.USERNAME)).isEmpty())
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+                    "username must be set", "username");
+        if (safeMap.get(Attributes.PASSWORD) == null || ((String) safeMap
+                .get(Attributes.PASSWORD)).isEmpty())
+            throw new KustvaktException(safeMap.get(Attributes.USERNAME),
+                    StatusCodes.ILLEGAL_ARGUMENT, "password must be set",
+                    "password");
+
+        String safePass = crypto
+                .validatePassphrase((String) safeMap.get(Attributes.PASSWORD));
+        String hash;
+        try {
+            hash = crypto.produceSecureHash(safePass);
+        }catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
+            KustvaktLogger.ERROR_LOGGER.error("Encryption error", e);
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+        }
+
+        KorAPUser user = User.UserFactory
+                .getUser((String) safeMap.get(Attributes.USERNAME));
+        UserDetails det = UserDetails.newDetailsIterator(safeMap);
+        user.setDetails(det);
+        user.setSettings(new UserSettings());
+        user.setAccountLocked(true);
+        URIParam param = new URIParam(crypto.createToken(), TimeUtils
+                .plusSeconds(BeanConfiguration.getBeans().getConfiguration()
+                        .getShortTokenTTL()).getMillis());
+        user.addField(param);
+        user.setPassword(hash);
+        try {
+            entHandler.createAccount(user);
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.CREATE_ACCOUNT_FAILED,
+                    user.toString());
+        }
+
+        auditing.audit(AuditRecord.serviceRecord(user.getUsername(),
+                StatusCodes.CREATE_ACCOUNT_SUCCESSFUL));
+        return user;
+    }
+
+    //todo:
+    private ShibUser createShibbUserAccount(Map<String, Object> attributes)
+            throws KustvaktException {
+        jlog.debug("creating shibboleth user account for user attr: {}",
+                attributes);
+        Map<String, Object> safeMap = crypto.validateMap(attributes);
+
+        //todo eppn non-unique.join with idp or use persistent_id as username identifier
+        ShibUser user = User.UserFactory
+                .getShibInstance((String) safeMap.get(Attributes.EPPN),
+                        (String) safeMap.get(Attributes.MAIL),
+                        (String) safeMap.get(Attributes.CN));
+        user.setAffiliation((String) safeMap.get(Attributes.EDU_AFFIL));
+        UserDetails det = UserDetails
+                .newDetailsIterator(new HashMap<String, Object>());
+        user.setDetails(det);
+        user.setSettings(new UserSettings());
+        user.setAccountCreation(TimeUtils.getNow().getMillis());
+        entHandler.createAccount(user);
+        return user;
+    }
+
+    /**
+     * link shibboleth and korap user account to one another.
+     *
+     * @param current    currently logged in user
+     * @param for_name   foreign user name the current account should be linked to
+     * @param transstrat transfer status of user data (details, settings, user queries)
+     *                   0 = the currently logged in data should be kept
+     *                   1 = the foreign account data should be kept
+     * @throws NotAuthorizedException
+     * @throws KustvaktException
+     */
+    // todo:
+    public void accountLink(User current, String for_name, int transstrat)
+            throws KustvaktException {
+        //        User foreign = entHandler.getAccount(for_name);
+
+        //        if (current.getAccountLink() == null && current.getAccountLink()
+        //                .isEmpty()) {
+        //            if (current instanceof KorAPUser && foreign instanceof ShibUser) {
+        //                if (transstrat == 1)
+        //                    current.transfer(foreign);
+        ////                foreign.setAccountLink(current.getUsername());
+        ////                current.setAccountLink(foreign.getUsername());
+        //                //                entHandler.purgeDetails(foreign);
+        //                //                entHandler.purgeSettings(foreign);
+        //            }else if (foreign instanceof KorAPUser
+        //                    && current instanceof ShibUser) {
+        //                if (transstrat == 0)
+        //                    foreign.transfer(current);
+        ////                current.setAccountLink(foreign.getUsername());
+        //                //                entHandler.purgeDetails(current);
+        //                //                entHandler.purgeSettings(current);
+        //                //                entHandler.purgeSettings(current);
+        //            }
+        //        entHandler.updateAccount(current);
+        //        entHandler.updateAccount(foreign);
+        //        }
+    }
+
+    @Override
+    public boolean updateAccount(User user) throws KustvaktException {
+        boolean result;
+        String key = cache_key(user.getUsername());
+        if (user instanceof DemoUser)
+            throw new KustvaktException(user.getId(),
+                    StatusCodes.REQUEST_INVALID,
+                    "account not updateable for demo user", user.getUsername());
+        else {
+            crypto.validate(user);
+            try {
+                result = entHandler.updateAccount(user) > 0;
+            }catch (KustvaktException e) {
+                KustvaktLogger.ERROR_LOGGER.error("Error ", e);
+                throw new WrappedException(e,
+                        StatusCodes.UPDATE_ACCOUNT_FAILED);
+            }
+        }
+        if (result) {
+            user_cache.remove(key);
+            auditing.audit(AuditRecord.serviceRecord(user.getId(),
+                    StatusCodes.UPDATE_ACCOUNT_SUCCESSFUL, user.toString()));
+        }
+        return result;
+    }
+
+    public boolean deleteAccount(User user) throws KustvaktException {
+        boolean result;
+        String key = cache_key(user.getUsername());
+        if (user instanceof DemoUser)
+            return true;
+        else {
+            try {
+                result = entHandler.deleteAccount(user.getId()) > 0;
+            }catch (KustvaktException e) {
+                KustvaktLogger.ERROR_LOGGER.error("Error ", e);
+                throw new WrappedException(e,
+                        StatusCodes.DELETE_ACCOUNT_FAILED);
+            }
+        }
+        if (result) {
+            user_cache.remove(key);
+            auditing.audit(AuditRecord.serviceRecord(user.getUsername(),
+                    StatusCodes.DELETE_ACCOUNT_SUCCESSFUL, user.toString()));
+        }
+        return result;
+    }
+
+    public Object[] validateResetPasswordRequest(String username, String email)
+            throws KustvaktException {
+        String mail, uritoken;
+        mail = crypto.validateEmail(email);
+        User ident;
+        try {
+            ident = entHandler.getAccount(username);
+            if (ident instanceof DemoUser)
+                //            throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
+                //                    "password reset now allowed for DemoUser", "");
+                throw new WrappedException(username,
+                        StatusCodes.PASSWORD_RESET_FAILED, username);
+        }catch (EmptyResultException e) {
+            throw new WrappedException(new KustvaktException(username,
+                    StatusCodes.ILLEGAL_ARGUMENT, "username not found",
+                    username), StatusCodes.PASSWORD_RESET_FAILED, username);
+        }
+
+        getUserDetails(ident);
+        KorAPUser user = (KorAPUser) ident;
+        if (!mail.equals(user.getDetails().getEmail()))
+            //            throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT,
+            //                    "invalid parameter: email", "email");
+            throw new WrappedException(new KustvaktException(user.getId(),
+                    StatusCodes.ILLEGAL_ARGUMENT, "email invalid", email),
+                    StatusCodes.PASSWORD_RESET_FAILED, email);
+        uritoken = crypto.encodeBase();
+        URIParam param = new URIParam(uritoken,
+                TimeUtils.plusHours(24).getMillis());
+        user.addField(param);
+
+        try {
+            entHandler.updateAccount(user);
+        }catch (KustvaktException e) {
+            KustvaktLogger.ERROR_LOGGER.error("Error ", e);
+            throw new WrappedException(e, StatusCodes.PASSWORD_RESET_FAILED);
+        }
+        return new Object[] { uritoken,
+                new DateTime(param.getUriExpiration()) };
+    }
+
+    public void updateUserSettings(User user, UserSettings settings)
+            throws KustvaktException {
+        if (user instanceof DemoUser)
+            return;
+        else {
+            crypto.validate(settings);
+            try {
+                entHandler.updateSettings(settings);
+            }catch (KustvaktException e) {
+                KustvaktLogger.ERROR_LOGGER.error("Error ", e);
+                throw new WrappedException(e,
+                        StatusCodes.UPDATE_ACCOUNT_FAILED);
+            }
+        }
+    }
+
+    public void updateUserDetails(User user, UserDetails details)
+            throws KustvaktException {
+        if (user instanceof DemoUser)
+            return;
+        else {
+            crypto.validate(details);
+            try {
+                entHandler.updateUserDetails(details);
+            }catch (KustvaktException e) {
+                KustvaktLogger.ERROR_LOGGER.error("Error ", e);
+                throw new WrappedException(e,
+                        StatusCodes.UPDATE_ACCOUNT_FAILED);
+            }
+        }
+    }
+
+    public UserDetails getUserDetails(User user) throws KustvaktException {
+        try {
+            if (user.getDetails() == null)
+                user.setDetails(entHandler.getUserDetails(user.getId()));
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
+        }
+        return user.getDetails();
+    }
+
+    public UserSettings getUserSettings(User user) throws KustvaktException {
+        try {
+            if (user.getSettings() == null)
+                user.setSettings(entHandler.getUserSettings(user.getId()));
+        }catch (KustvaktException e) {
+            throw new WrappedException(e, StatusCodes.GET_ACCOUNT_FAILED);
+        }
+        return user.getSettings();
+    }
+
+    private String cache_key(String input) {
+        return crypto.hash(KEY + "@" + input);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/LoginCounter.java b/src/main/java/de/ids_mannheim/korap/security/auth/LoginCounter.java
new file mode 100644
index 0000000..c38bc02
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/LoginCounter.java
@@ -0,0 +1,62 @@
+package de.ids_mannheim.korap.security.auth;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 11/11/2014
+ */
+public class LoginCounter {
+
+    private static Logger jlog = KustvaktLogger.initiate(LoginCounter.class);
+    private final Map<String, Long[]> failedLogins;
+    private KustvaktConfiguration config;
+
+    public LoginCounter(KustvaktConfiguration config) {
+        jlog.debug("init login counter for authentication management");
+        this.config = config;
+        this.failedLogins = new HashMap<>();
+    }
+
+    public void resetFailedCounter(String username) {
+        failedLogins.remove(username);
+    }
+
+    public void registerFail(String username) {
+        long expires = TimeUtils.plusSeconds(config.getLoginAttemptTTL())
+                .getMillis();
+        long fail = 1;
+        Long[] set = failedLogins.get(username);
+        if (set != null)
+            fail = set[0] + 1;
+        else
+            set = new Long[2];
+        set[0] = fail;
+        set[1] = expires;
+
+        failedLogins.put(username, set);
+        jlog.warn("user failed to login ({}) ",
+                Arrays.asList(failedLogins.get(username)));
+    }
+
+    public boolean validate(String username) {
+        Long[] set = failedLogins.get(username);
+        if (set != null) {
+            if (TimeUtils.isPassed(set[1])) {
+                failedLogins.remove(username);
+                return true;
+            }else if (set[0] < config.getLoginAttemptNum())
+                return true;
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/OpenIDconnectAuthentication.java b/src/main/java/de/ids_mannheim/korap/security/auth/OpenIDconnectAuthentication.java
new file mode 100644
index 0000000..cae5e2d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/OpenIDconnectAuthentication.java
@@ -0,0 +1,81 @@
+package de.ids_mannheim.korap.security.auth;
+
+import com.nimbusds.jwt.SignedJWT;
+import de.ids_mannheim.korap.config.JWTSigner;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.handlers.OAuthDb;
+import de.ids_mannheim.korap.interfaces.AuthenticationIface;
+import de.ids_mannheim.korap.interfaces.PersistenceClient;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.StringUtils;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+import java.text.ParseException;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 12/11/2014
+ */
+public class OpenIDconnectAuthentication implements AuthenticationIface {
+
+    private OAuthDb database;
+    private KustvaktConfiguration config;
+
+    public OpenIDconnectAuthentication(KustvaktConfiguration config,
+            PersistenceClient client) {
+        this.database = new OAuthDb(client);
+        this.config = config;
+    }
+
+    @Cacheable(value = "id_tokens", key = "#authToken")
+    @Override
+    public TokenContext getUserStatus(String authToken)
+            throws KustvaktException {
+        authToken = StringUtils.stripTokenType(authToken);
+        return this.database.getContext(authToken);
+    }
+
+    @Override
+    public TokenContext createUserSession(User user, Map<String, Object> attr)
+            throws KustvaktException {
+        JWTSigner signer = new JWTSigner(
+                ((String) attr.get(Attributes.CLIENT_SECRET)).getBytes(),
+                config.getIssuer(), config.getTokenTTL());
+        TokenContext c = new TokenContext(user.getUsername());
+        SignedJWT jwt = signer.createJWT(user, attr);
+        try {
+            c.setExpirationTime(jwt.getJWTClaimsSet().getExpirationTimeClaim());
+        }catch (ParseException e) {
+            throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+        }
+        c.setTokenType(Attributes.OPENID_AUTHENTICATION);
+        c.setToken(jwt.serialize());
+        CacheManager.getInstance().getCache("id_tokens")
+                .put(new Element(c.getToken(), c));
+        return c;
+    }
+
+    @CacheEvict(value = "id_tokens", key = "#token")
+    @Override
+    public void removeUserSession(String token) throws KustvaktException {
+        // emit token from cache only
+    }
+
+    @Override
+    public TokenContext refresh(TokenContext context) throws KustvaktException {
+        throw new UnsupportedOperationException("method not supported");
+    }
+
+    @Override
+    public String getIdentifier() {
+        return Attributes.OPENID_AUTHENTICATION;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/SessionAuthentication.java b/src/main/java/de/ids_mannheim/korap/security/auth/SessionAuthentication.java
new file mode 100644
index 0000000..99ebd89
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/SessionAuthentication.java
@@ -0,0 +1,93 @@
+package de.ids_mannheim.korap.security.auth;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationIface;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * implementation of the AuthenticationIface to handle korap authentication
+ * internals
+ *
+ * @author hanl
+ */
+public class SessionAuthentication implements AuthenticationIface {
+
+    private static Logger jlog = KustvaktLogger
+            .initiate(SessionAuthentication.class);
+    private SessionFactory sessions;
+    private ScheduledThreadPoolExecutor scheduled;
+    private EncryptionIface crypto;
+    private KustvaktConfiguration config;
+
+    public SessionAuthentication(KustvaktConfiguration config,
+            EncryptionIface crypto) {
+        jlog.info("initialize session authentication handler");
+        this.crypto = crypto;
+        this.config = config;
+        this.scheduled = new ScheduledThreadPoolExecutor(1);
+        this.sessions = new SessionFactory(this.config.isAllowMultiLogIn(),
+                this.config.getInactiveTime());
+        this.scheduled.scheduleAtFixedRate(this.sessions,
+                this.config.getInactiveTime() / 2,
+                this.config.getInactiveTime(), TimeUnit.SECONDS);
+    }
+
+    @Override
+    public TokenContext getUserStatus(String authenticationToken)
+            throws KustvaktException {
+        jlog.debug("retrieving user session for user '{}'",
+                authenticationToken);
+        if (authenticationToken == null)
+            throw new KustvaktException(StatusCodes.PERMISSION_DENIED);
+        return this.sessions.getSession(authenticationToken);
+    }
+
+    @Override
+    public TokenContext createUserSession(User user, Map attr)
+            throws KustvaktException {
+        DateTime now = TimeUtils.getNow();
+        DateTime ex = TimeUtils
+                .getExpiration(now.getMillis(), config.getExpiration());
+        String token = crypto
+                .createToken(true, user.getUsername(), now.getMillis());
+        TokenContext ctx = new TokenContext(user.getUsername());
+        ctx.setUsername(user.getUsername());
+        ctx.setTokenType(Attributes.SESSION_AUTHENTICATION);
+        ctx.setToken(token);
+        ctx.setExpirationTime(ex.getMillis());
+        ctx.setHostAddress(attr.get(Attributes.HOST).toString());
+        ctx.setUserAgent(attr.get(Attributes.USER_AGENT).toString());
+        this.sessions.putSession(token, ctx);
+        jlog.info("create session for user: " + user.getUsername());
+        return ctx;
+    }
+
+    @Override
+    public void removeUserSession(String token) {
+        this.sessions.removeSession(token);
+    }
+
+    @Override
+    public TokenContext refresh(TokenContext context) throws KustvaktException {
+        throw new UnsupportedOperationException("method not supported");
+    }
+
+    @Override
+    public String getIdentifier() {
+        return Attributes.SESSION_AUTHENTICATION;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/SessionFactory.java b/src/main/java/de/ids_mannheim/korap/security/auth/SessionFactory.java
new file mode 100644
index 0000000..26e316a
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/SessionFactory.java
@@ -0,0 +1,171 @@
+package de.ids_mannheim.korap.security.auth;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.DemoUser;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.utils.ConcurrentMultiMap;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * session object to hold current user sessions and track inactive time to close
+ * unused sessions. Inactive sessions are not enforced until user makes a
+ * request through thrift
+ *
+ * @author hanl
+ */
+public class SessionFactory implements Runnable {
+
+    private static Logger jlog = KustvaktLogger.initiate(SessionFactory.class);
+
+    private final ConcurrentMap<String, TokenContext> sessionsObject;
+    private final ConcurrentMap<String, DateTime> timeCheck;
+    private final ConcurrentMultiMap<String, String> loggedInRecord;
+    //    private final ConcurrentMultiMap<String, Long> failedLogins;
+    private final boolean multipleEnabled;
+    private final int inactive;
+
+    public SessionFactory(boolean multipleEnabled, int inactive) {
+        jlog.debug("allow multiple sessions per user: '{}'", multipleEnabled);
+        this.multipleEnabled = multipleEnabled;
+        this.inactive = inactive;
+        this.sessionsObject = new ConcurrentHashMap<>();
+        this.timeCheck = new ConcurrentHashMap<>();
+        this.loggedInRecord = new ConcurrentMultiMap<>();
+    }
+
+    public boolean hasSession(TokenContext context) {
+        if (context.getUsername().equalsIgnoreCase(DemoUser.DEMOUSER_NAME))
+            return false;
+        if (loggedInRecord.containsKey(context.getUsername()) && !loggedInRecord
+                .get(context.getUsername()).isEmpty())
+            return true;
+        return false;
+    }
+
+    @Cacheable("session")
+    public TokenContext getSession(String token) throws KustvaktException {
+        jlog.debug("logged in users: {}", loggedInRecord);
+        TokenContext context = sessionsObject.get(token);
+        if (context != null) {
+            if (isUserSessionValid(token)) {
+                resetInterval(token);
+                return context;
+            }else
+                throw new KustvaktException(StatusCodes.EXPIRED);
+
+        }else
+            throw new KustvaktException(StatusCodes.PERMISSION_DENIED);
+    }
+
+    //todo: ?!
+    @CacheEvict(value = "session", key = "#session.token")
+    public void putSession(final String token, final TokenContext activeUser)
+            throws KustvaktException {
+        if (!hasSession(activeUser) | multipleEnabled) {
+            loggedInRecord.put(activeUser.getUsername(), token);
+            sessionsObject.put(token, activeUser);
+            timeCheck.put(token, TimeUtils.getNow());
+        }else {
+            removeAll(activeUser);
+            throw new KustvaktException(StatusCodes.ALREADY_LOGGED_IN);
+        }
+    }
+
+    public void removeAll(final TokenContext activeUser) {
+        for (String existing : loggedInRecord.get(activeUser.getUsername())) {
+            timeCheck.remove(existing);
+            sessionsObject.remove(existing);
+        }
+        loggedInRecord.remove(activeUser.getUsername());
+    }
+
+    @CacheEvict(value = "session", key = "#session.token")
+    public void removeSession(String token) {
+        String username = sessionsObject.get(token).getUsername();
+        loggedInRecord.remove(username, token);
+        if (loggedInRecord.get(username).isEmpty())
+            loggedInRecord.remove(username);
+        timeCheck.remove(token);
+        sessionsObject.remove(token);
+    }
+
+    /**
+     * reset inactive time interval to 0
+     *
+     * @param token
+     */
+    private void resetInterval(String token) {
+        timeCheck.put(token, TimeUtils.getNow());
+    }
+
+    /**
+     * if user possesses a valid non-expired session token
+     *
+     * @param token
+     * @return validity of user to request a backend function
+     */
+    private boolean isUserSessionValid(String token) {
+        if (timeCheck.containsKey(token)) {
+            if (TimeUtils.plusSeconds(timeCheck.get(token).getMillis(),
+                    inactive).isAfterNow()) {
+                jlog.debug("user has session");
+                return true;
+            }else
+                jlog.debug("user with token {} has an invalid session", token);
+        }
+        return false;
+    }
+
+    /**
+     * clean inactive sessions from session object
+     * TODO: persist userdata to database when session times out!
+     */
+    private void timeoutMaintenance() {
+        jlog.debug("running session cleanup thread");
+        Set<String> inactive = new HashSet<>();
+        for (Entry<String, DateTime> entry : timeCheck.entrySet()) {
+            if (!isUserSessionValid(entry.getKey())) {
+                TokenContext user = sessionsObject.get(entry.getKey());
+                jlog.debug("removing user session for user {}",
+                        user.getUsername());
+                inactive.add(user.getUsername());
+                removeSession(entry.getKey());
+            }
+        }
+        if (inactive.size() > 0)
+            jlog.debug("removing inactive user session for users '{}' ",
+                    inactive);
+
+        //        keys:
+        //        for (String key : failedLogins.getKeySet()) {
+        //            DateTime d = new DateTime(failedLogins.get(key).get(1));
+        //            if (d.isBeforeNow()) {
+        //                failedLogins.remove(key);
+        //                jlog.info("removed failed login counts due to expiration for user {}", key);
+        //                continue keys;
+        //            }
+        //        }
+    }
+
+    /**
+     * run cleanup-thread
+     */
+    @Override
+    public void run() {
+        timeoutMaintenance();
+        jlog.debug("logged users: {}", loggedInRecord.toString());
+
+    }
+}