diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 610c10ccf5c6a61d2610e4be2b3fdefe9e51fcde..31a968fec3b241c1d54e3257898a92a6e781fe71 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -6,6 +6,7 @@ import { WelcomeRoutes } from './welcome/welcome.routes';
 import {ServiceUnavailableRoutes} from './service-unavailable/service-unavailable.routes';
 import {PageNotFoundComponent} from './shared/page-not-found/page-not-found.component';
 import {LoginSuccessComponent} from './auth/login-success/login-success.component';
+import {LinkAccountComponent} from './welcome/link-account/link-account.component';
 
 const appRoutes: Routes = [
     ...WelcomeRoutes,
@@ -13,6 +14,7 @@ const appRoutes: Routes = [
     ...ServiceUnavailableRoutes,
     { path: 'notfound', component: PageNotFoundComponent },
     { path: 'login-success', component: LoginSuccessComponent },
+    { path: 'login-linking', component: LinkAccountComponent},
     { path: '**', redirectTo: '/welcome' },
 
 ];
diff --git a/src/app/auth/auth.service.spec.ts b/src/app/auth/auth.service.spec.ts
index e9eef99417406df03337405ecd381adfb6f5a3c7..170ece28132bc78a75fd54ad8338c48914b82ea4 100644
--- a/src/app/auth/auth.service.spec.ts
+++ b/src/app/auth/auth.service.spec.ts
@@ -3,19 +3,18 @@ import {TestBed, waitForAsync} from '@angular/core/testing';
 import {AuthService} from './auth.service';
 import {AppConfigService, ConfigurationService} from '../service';
 import {JwtHelperService} from '@auth0/angular-jwt';
-import {HttpClientTestingModule} from '@angular/common/http/testing';
+import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
 import {Role, UserRole} from '../model/userrole';
 import {ProfileService} from '../service/profile.service';
 import {Observable, of} from 'rxjs';
-import { Configuration } from '../model/configuration';
-import { HttpHandler } from '@angular/common/http';
+import {Configuration} from '../model/configuration';
 
 describe('Service: Auth', () => {
     let authService: AuthService;
     let appConfigServiceSpy: jasmine.SpyObj<AppConfigService>;
     let jwtHelperServiceSpy: jasmine.SpyObj<JwtHelperService>;
     let maintenanceServiceSpy: jasmine.SpyObj<ConfigurationService>;
-
+    let httpMock: HttpTestingController;
     let store: any = {};
 
     beforeEach(waitForAsync(() => {
@@ -23,7 +22,8 @@ describe('Service: Auth', () => {
             config: {
                 apiUrl: 'http://api.url',
                 tokenName: 'token',
-            }
+            },
+            getTestInstanceModalKey: () => 'testModalKey'
         };
         const jwtSpy = jasmine.createSpyObj('JwtHelperService', ['decodeToken', 'isTokenExpired']);
         jwtSpy.decodeToken.and.returnValue({
@@ -41,19 +41,19 @@ describe('Service: Auth', () => {
 
         class MockConfigurationService {
             protected uri: string;
-        
+
             constructor() {
                 this.uri = 'http://localhost/api';
             }
-        
+
             public getApiUrl(): string {
                 return 'http://localhost/api';
             }
-        
+
             public getConfiguration(): Observable<Configuration> {
                 return of<Configuration>();
             }
-        
+
             public updateConfiguration(configuration: Configuration): Observable<any> {
                 return of<Configuration>();
             }
@@ -84,6 +84,7 @@ describe('Service: Auth', () => {
             ],
         });
 
+        httpMock = TestBed.inject(HttpTestingController)
         authService = TestBed.get(AuthService);
         authService.profile = [userRole, userRole2]
         appConfigServiceSpy = TestBed.get(AppConfigService);
@@ -104,6 +105,10 @@ describe('Service: Auth', () => {
         });
 
     }));
+    afterEach(() => {
+        httpMock.verify();
+        store = {};
+    });
 
     it('should create service', () => {
         expect(authService).toBeTruthy();
@@ -180,8 +185,11 @@ describe('Service: Auth', () => {
     });
 
     it('should remove token on logout', () => {
+        store['oidc-token'] = 'some-oidc-token';
         authService.logout();
         expect(store['token']).not.toBeDefined();
+        const req = httpMock.expectOne('http://api.url/oidc/logout/some-oidc-token');
+        req.flush({});
     });
 
     it('should be logged in when token is present and valid', () => {
@@ -197,4 +205,58 @@ describe('Service: Auth', () => {
         expect(r).toEqual(false);
     });
 
+    it('should store token and oidc token in localStorage', () => {
+        authService.storeToken('abc123');
+        expect(store['token']).toEqual('abc123');
+
+        authService.storeOidcToken('oidc456');
+        expect(store['oidc-token']).toEqual('oidc456');
+    });
+
+    it('should remove roles from localStorage', () => {
+        store['rolesToken'] = 'some_roles';
+        authService.removeRoles();
+        expect(store['rolesToken']).toBeUndefined();
+    });
+
+    it('should load and parse roles from localStorage', () => {
+        const roles = [{domainId: 1, role: Role.ROLE_USER, domainName: 'x'}];
+        store['rolesToken'] = JSON.stringify(roles);
+
+        const result = authService.loadRoles();
+        expect(result.length).toEqual(1);
+        expect(result[0].role).toEqual(Role.ROLE_USER);
+    });
+
+    it('should assign loaded roles to profile', () => {
+        const roles = [{domainId: 2, role: Role.ROLE_DOMAIN_ADMIN, domainName: 'x'}];
+        store['rolesToken'] = JSON.stringify(roles);
+        authService.loadAndSaveRoles();
+        expect(authService.profile[0].role).toEqual(Role.ROLE_DOMAIN_ADMIN);
+    });
+    it('should stringify and store roles', () => {
+        const roles = [new UserRole()];
+        roles[0].domainId = 1;
+        roles[0].role = Role.ROLE_USER;
+        roles[0].domainName = 'dom1';
+
+        authService.storeRoles(roles);
+        expect(store['rolesToken']).toContain('ROLE_USER');
+    });
+    it('should get global role from token', () => {
+        const result = authService.getGlobalRole();
+        expect(result).toContain('ROLE_SYSTEM_ADMIN');
+    });
+
+    it('should handle login error with catchError', waitForAsync(() => {
+        authService.login('user', 'pass').subscribe({
+            next: () => fail('Expected error'),
+            error: (err) => {
+                expect(err.status).toEqual(401);
+            }
+        });
+
+        const req = httpMock.expectOne('http://api.url/auth/basic/login');
+        req.flush({ message: 'Invalid credentials' }, { status: 401, statusText: 'Unauthorized' });
+    }));
 });
diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts
index a3426c27b2b23a95efaec88574ee9c9078b05f85..6156c8e479cfd788ed7808556017f30cb57bc40f 100644
--- a/src/app/auth/auth.service.ts
+++ b/src/app/auth/auth.service.ts
@@ -1,13 +1,11 @@
-import {BehaviorSubject, Observable, Subject, throwError as observableThrowError, of} from 'rxjs';
+import {BehaviorSubject, Observable, of, Subject, throwError as observableThrowError} from 'rxjs';
 import {catchError, debounceTime, map} from 'rxjs/operators';
 import {Injectable} from '@angular/core';
 import {AppConfigService, ConfigurationService} from '../service';
 import {JwtHelperService} from '@auth0/angular-jwt';
 import {HttpClient, HttpHeaders} from '@angular/common/http';
-import {User} from '../model';
 import {ProfileService} from '../service/profile.service';
 import {Role, UserRole} from '../model/userrole';
-import {interval, Subscription} from 'rxjs';
 
 
 export class DomainRoles {
@@ -17,10 +15,6 @@ export class DomainRoles {
     ) {
     }
 
-    public getDomainId(): number {
-        return this.domainId;
-    }
-
     public getRoles(): string[] {
         return this.roles;
     }
@@ -183,15 +177,6 @@ export class AuthService {
         return this.jwtHelper.decodeToken(token).global_role;
     }
 
-    public getDomainsRoles() {
-        const token = this.getToken();
-        if (token == null) {
-            return null;
-        }
-        return this.jwtHelper.decodeToken(token).roles;
-
-    }
-
     public getDomainRoles(): Map<number, DomainRoles> {
         const domainRolesMap: Map<number, DomainRoles> = new Map<number, DomainRoles>();
 
@@ -253,6 +238,49 @@ export class AuthService {
         return domainsWithRole;
     }
 
+    public oidcLinkingLogin(oidcToken: string,
+                            email: string,
+                            password: string,
+                            uuid: string,
+                            firstName: string,
+                            lastName: string) {
+        const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'});
+
+        return this.http.post(this.appConfig.config.apiUrl + '/oidc/link',
+            JSON.stringify(
+                {
+                    'oidcToken': oidcToken,
+                    'email': email,
+                    'password': password,
+                    'uuid': uuid,
+                    'firstName': firstName,
+                    'lastName': lastName,
+                }
+            ),
+            {headers: headers}).pipe(
+            debounceTime(1000),
+            map((res: Response) => {
+                    const token = res && res['token'];
+                    const oidcToken = res && res['oidcToken'];
+                    if (token && oidcToken) {
+                        this.storeToken(token);
+                        this.storeOidcToken(oidcToken);
+                        this.loginUsingSsoService = false;
+                        this.isLoggedInSubject.next(true);
+                        this.profileService.getRoles().subscribe(profile => {
+                            this.profile = profile
+                            this.storeRoles(profile);
+                            return true;
+                        })
+                    } else {
+                        this.isLoggedInSubject.next(false);
+                        return false;
+                    }
+                }
+            ),
+        )
+    }
+
     public login(username: string, password: string): Observable<boolean> {
         // hack so test instance modal is shown onl after login
         localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True');
@@ -306,49 +334,6 @@ export class AuthService {
             }));
     }
 
-    public propagateSSOLogin(userid: string): Observable<boolean> {
-        console.debug('propagateSSOLogin');
-        console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl);
-        console.debug('propagateSSOLogin ' + this.appConfig.config.apiUrl + '/auth/sso/login');
-        console.debug('propagateSSOLogin ' + userid);
-        // hack so test instance modal is shown onl after login
-        localStorage.setItem(this.appConfig.getTestInstanceModalKey(), 'True');
-
-        if (this.maintenance) {
-            this.isLoggedInSubject.next(false);
-            return of(false);
-        }
-
-        const headers = new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json'});
-        return this.http.post(this.appConfig.config.apiUrl + '/auth/sso/login',
-            JSON.stringify({'userid': userid}), {headers: headers}).pipe(
-            debounceTime(10000),
-            map((response: Response) => {
-                console.debug('SSO login response: ' + response);
-                // login successful if there's a jwt token in the response
-                const token = response && response['token'];
-
-                if (token) {
-                    this.storeToken(token);
-                    console.debug('SSO AUTH | User: ' + this.getUsername());
-                    console.debug('SSO AUTH | Domains: ' + this.getDomains());
-                    console.debug('SSO AUTH | Roles: ' + this.getRoles());
-                    console.debug('SSO AUTH | DomainRoles: ' + this.getDomainRoles());
-                    this.loginUsingSsoService = true;
-                    this.isLoggedInSubject.next(true);
-                    return true;
-                } else {
-                    // return false to indicate failed login
-                    this.isLoggedInSubject.next(false);
-                    return false;
-                }
-            }),
-            catchError((error) => {
-                console.error('SSO login error: ' + error.error['message']);
-                return observableThrowError(error);
-            }));
-    }
-
     public logout(): void {
         const oidcToken = this.getOidcToken();
         this.refresh = null;
@@ -366,6 +351,11 @@ export class AuthService {
         }
     }
 
+    public oidcLogout(oidcToken: string): void {
+        this.http.get(this.appConfig.config.apiUrl + '/oidc/logout/' + oidcToken).subscribe(() => {
+        })
+    }
+
     public isLogged(): boolean {
         const token = this.getToken();
         if (token == null) {
@@ -374,11 +364,6 @@ export class AuthService {
         return (token ? !this.jwtHelper.isTokenExpired(token) : false);
     }
 
-    get isLoggedIn$(): Observable<boolean> {
-        return this.isLoggedInSubject.pipe(
-            debounceTime(100), // use debounceTime to aggregate multiple emissions https://rxjs.dev/api/operators/debounceTime
-        );
-    }
 
     public getDomainIds(): number[] {
         return Array.from(new Set(this.profile.map(ur => ur.domainId)));
diff --git a/src/app/welcome/link-account/link-account.component.css b/src/app/welcome/link-account/link-account.component.css
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.css
@@ -0,0 +1 @@
+
diff --git a/src/app/welcome/link-account/link-account.component.html b/src/app/welcome/link-account/link-account.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..1ab303ed37f4a43c7b9de3afd3519ec4b878ef37
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.html
@@ -0,0 +1,51 @@
+<div style="display: flex; justify-content: center;">
+    <div style="
+margin-top: 50px;
+ width: 60%
+" class="panel panel-default">
+        <div class="panel-heading">{{ 'ACCOUNT_LINKING.HEADER' | translate }}</div>
+        <div class="panel-body">
+            <form *ngIf="user"
+                  class="form-horizontal" #userDetailsForm="ngForm">
+                <div>
+                    <p>
+                        {{ 'ACCOUNT_LINKING.INFO' | translate }}
+                    </p>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.FIRST_NAME' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.firstname }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.LAST_NAME' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.lastname }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">{{ 'USER_DETAILS.EMAIL' | translate }}</label>
+                    <div class="col-sm-10">
+                        <p class="form-control-static">{{ user.email }}</p>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label for="password" class="col-sm-2 control-label">{{ 'PASSWORD.PASSWORD' | translate }}</label>
+                    <div class="col-sm-10">
+                        <input type="password" class="form-control" id="password"
+                               name="password" [(ngModel)]="password">
+                    </div>
+                </div>
+                <button type="submit" class="btn btn-primary"
+                        (click)="submit()">{{ 'ACCOUNT_LINKING.CONFIRM' | translate }}
+                </button>
+
+            </form>
+            <br>
+        </div>
+    </div>
+</div>
diff --git a/src/app/welcome/link-account/link-account.component.spec.ts b/src/app/welcome/link-account/link-account.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0b0a9f98c9b851dbd8f044e900df4b02595ca2ca
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.spec.ts
@@ -0,0 +1,57 @@
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {LinkAccountComponent} from './link-account.component';
+import {ActivatedRoute} from '@angular/router';
+import {of} from 'rxjs';
+import {AuthService} from '../../auth/auth.service';
+import {TranslateFakeLoader, TranslateLoader, TranslateModule} from '@ngx-translate/core';
+
+describe('LinkAccountComponent', () => {
+    let component: LinkAccountComponent;
+    let fixture: ComponentFixture<LinkAccountComponent>;
+
+    beforeEach(async () => {
+        await TestBed.configureTestingModule({
+            declarations: [LinkAccountComponent],
+            imports: [TranslateModule.forRoot({
+                loader: {
+                    provide: TranslateLoader,
+                    useClass: TranslateFakeLoader
+                }
+            })],
+            providers: [
+                {
+                    provide: ActivatedRoute,
+                    useValue: {
+                        queryParams: of({
+                            oidc_token: 'mocked.jwt.token'
+                        }),
+                        snapshot: {
+                            paramMap: {
+                                get: () => null
+                            }
+                        }
+                    }
+                },
+                {
+                    provide: AuthService,
+                    useValue: {
+                        isLogged: () => true,
+                        oidcLogout: jasmine.createSpy('oidcLogout'),
+                        oidcLinkingLogin: jasmine.createSpy('oidcLinkingLogin').and.returnValue(of({}))
+                    }
+                },
+
+            ]
+        })
+            .compileComponents();
+
+        fixture = TestBed.createComponent(LinkAccountComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/src/app/welcome/link-account/link-account.component.ts b/src/app/welcome/link-account/link-account.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1f4f967c4fb0b2b058724e575469136354574555
--- /dev/null
+++ b/src/app/welcome/link-account/link-account.component.ts
@@ -0,0 +1,66 @@
+import {Component, OnDestroy, OnInit} from '@angular/core';
+import {User} from '../../model';
+import {ActivatedRoute, Router} from '@angular/router';
+import jwtDecode from 'jwt-decode';
+import {AuthService} from '../../auth/auth.service';
+
+
+@Component({
+    selector: 'app-link-account',
+    templateUrl: './link-account.component.html',
+    styleUrl: './link-account.component.css'
+})
+export class LinkAccountComponent implements OnInit, OnDestroy {
+    public user: User;
+    private token: string;
+    public password: string;
+
+    constructor(
+        private readonly route: ActivatedRoute,
+        private readonly authService: AuthService,
+        private readonly router: Router
+    ) {
+    }
+
+    ngOnDestroy() {
+        if (!this.authService.isLogged()) {
+            this.authService.oidcLogout(this.token)
+        }
+    }
+
+    ngOnInit() {
+        this.route.queryParams.subscribe(param => {
+            this.token = param['oidc_token'];
+            const decoded: TokenPayload = jwtDecode<TokenPayload>(this.token);
+            this.user = new User();
+            this.user.username = decoded.sub;
+            this.user.firstname = decoded.given_name;
+            this.user.lastname = decoded.family_name;
+            this.user.email = decoded.email;
+        })
+
+    }
+
+    public submit(): void {
+        this.authService.oidcLinkingLogin(
+            this.token,
+            this.user.email,
+            this.password,
+            this.user.username,
+            this.user.firstname,
+            this.user.lastname,
+        ).subscribe(
+            () => {
+                this.router.navigate(['/']);
+            }
+        )
+    }
+}
+
+
+interface TokenPayload {
+    sub: string;
+    email: string;
+    given_name: string;
+    family_name: string;
+}
diff --git a/src/app/welcome/login/login.component.ts b/src/app/welcome/login/login.component.ts
index 62e1fc26688f7417969f4cd671b4a93317e49e2a..6971d77629a2e0592e8eb6347b02f56078c5d1c1 100644
--- a/src/app/welcome/login/login.component.ts
+++ b/src/app/welcome/login/login.component.ts
@@ -64,8 +64,11 @@ export class LoginComponent implements OnInit {
         );
     }
 
+    // only for use in linking accounts
     public triggerOIDC() {
-        window.location.href = this.appConfig.getOidcUrl();
+        if (this.configuration.maintenance) {
+            window.location.href = this.appConfig.getOidcUrl();
+        }
     }
 
     public sendResetNotification() {
diff --git a/src/app/welcome/welcome.module.ts b/src/app/welcome/welcome.module.ts
index 08fc1c81e12392217f5c403431925659eea6afb8..e212c4f059d959724211aa423e911d38b087f067 100644
--- a/src/app/welcome/welcome.module.ts
+++ b/src/app/welcome/welcome.module.ts
@@ -20,38 +20,41 @@ import {TranslateModule} from '@ngx-translate/core';
 import {PasswordResetComponent} from './passwordreset/password-reset.component';
 import {PasswordStrengthMeterComponent} from 'angular-password-strength-meter';
 import {PolicySubpageComponent} from './policy-subpage/policy-subpage.component';
+import {LinkAccountComponent} from './link-account/link-account.component';
 
 @NgModule({
-  declarations: [
-    WelcomeComponent,
-    LoginComponent,
-    LogoutComponent,
-    RegistrationComponent,
-    ProfileComponent,
-    CompleteComponent,
-    TermsAcceptanceComponent,
-    PasswordResetComponent,
-    PolicySubpageComponent
-  ],
-  imports: [
-    FormsModule,
-    ReactiveFormsModule,
-    CommonModule,
-    RouterModule,
-    SharedModule,
-    PipesModule,
-    AppMarketModule,
-    PasswordStrengthMeterComponent,
-    TranslateModule.forChild()
-  ],
-  exports: [
-    WelcomeComponent
-  ],
-  providers: [
-    RegistrationService,
-    UserService,
-    ChangelogService,
-    ContentDisplayService
-  ]
+    declarations: [
+        WelcomeComponent,
+        LoginComponent,
+        LogoutComponent,
+        RegistrationComponent,
+        ProfileComponent,
+        CompleteComponent,
+        TermsAcceptanceComponent,
+        PasswordResetComponent,
+        PolicySubpageComponent,
+        LinkAccountComponent
+    ],
+    imports: [
+        FormsModule,
+        ReactiveFormsModule,
+        CommonModule,
+        RouterModule,
+        SharedModule,
+        PipesModule,
+        AppMarketModule,
+        PasswordStrengthMeterComponent,
+        TranslateModule.forChild()
+    ],
+    exports: [
+        WelcomeComponent
+    ],
+    providers: [
+        RegistrationService,
+        UserService,
+        ChangelogService,
+        ContentDisplayService
+    ]
 })
-export class WelcomeModule {}
+export class WelcomeModule {
+}