DeBiese-Code-Bucket
DeBiese-Code-Bucket copied to clipboard
How do you perform logout
Hi Ruben, I posted this question on your blog and Stackoverflow : https://stackoverflow.com/questions/48974995/logout-facility-in-angular-5-application-when-using-windows-authentication
How do you perform logout()
I'm gonna put my answer to your question on the blogpost here as well. Did you get anywhere with it?
I haven’t given much thought to a logout functionality. I would implement windows authentication only in an intranet environment. Not for a public facing website. And as such, I wouldn’t even want to get the login prompt.
Be that as it may, I searched around a bit, and found something that might be usefull in working towards a solution. Beware: I’m providing some information based on assumptions, I have not tested the following myself. Given the information here: https://en.wikipedia.org/wiki/Basic_access_authentication a possible solution would be to have your server return a 401 with WWW-Authenticate field.
I see 2 options (there can be other and/or better ones):
Keep track of users on your web api (by eg using a custom actionfilterattribute on your controllers/methods). A first time a user makes a request, you return the 401. You can have a caching mechanism to retain the user x-amount of time and/or a logout method in your api.
Implement a logout on your client application. Keep track in your application if the user is in the logged in/logged out status. Send your first request to your web api without addding the credentials to the header. This means that you should find a way for the interceptor not to work on that first request. Easiest way to accomplish this is by starting the app on a login page, clicking the login button sends a request without using the interceptor and you’ll get prompted.
I’m not sure if this helps. If you do try one of those, please let me know how you solved it. I might just add the solution to the blogpost and example code.
I have the following setup which I am not completely happy with but kind of works:
1- Lets say User logs in successfully. Some user information like current user and flags gets stored in localStorage using a UserService:
import { Injectable } from '@angular/core';
import { ReplaySubject } from 'rxjs/ReplaySubject'
import { IUserInfo } from '../../models/IUserInfo';
import { LocalStorageService } from '../../services/local-storage-service';
const userKey = 'user';
@Injectable()
export class UserService {
status: ReplaySubject<boolean> = new ReplaySubject<boolean>();
private _hasSignedOut = false;
constructor(private localStorageService: LocalStorageService) { this.status.next(this.getUser() != null); }
setUser = (user) => {
if (user) {
this.localStorageService.setItem(userKey, user);
this.status.next(true);
} else {
this.localStorageService.removeItem(userKey);
this.status.next(false);
}
}
get SignOutFlag(): boolean { return this._hasSignedOut; }
set SignOutFlag(flg: boolean) { this._hasSignedOut = flg; }
getUser = () => this.localStorageService.getItem(userKey);
removeUser = () => this.setUser(null);
}
and the LocalStorageService is
import { Injectable } from '@angular/core';
@Injectable()
export class LocalStorageService {
private cachePrefix = 'cacheData.';
constructor() { }
setItem(data, value) {
const jsonValue = JSON.stringify(value);
localStorage.setItem(`${this.cachePrefix}${data}`, jsonValue);
}
getItem(data) {
const response = localStorage.getItem(`${this.cachePrefix}${data}`);
return JSON.parse(response);
}
removeItem(data) {
localStorage.removeItem(`${this.cachePrefix}${data}`);
}
clearEntireCache() {
localStorage.clear();
}
}
Now a typical Signout >
public signOut(): void {
this.userService.removeUser();
this.localStorageService.clearEntireCache();
document.execCommand('ClearAuthenticationCache', false);
this.userService.SignOutFlag = true;
this.router.navigate(['/login']);
}
Now, I have a very simple WinLoginComponent:
<div class="win-login-box">
<a [routerLink]="" (click)="login()">Login</a>
</div>
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { WinAuthService } from '../../services/auth/win-auth.service';
import { UserService } from '../../services/auth/user.service';
@Component({
selector: 'app-win-login',
templateUrl: './win-login.component.html',
styleUrls: ['./win-login.component.css']
})
export class WinLoginComponent implements OnInit {
constructor(private router: Router, public winAuthService: WinAuthService, public userService: UserService) { }
ngOnInit() {
}
login() {
console.log('Trying to login back');
this.winAuthService.loginAfterSignout()
.map(userData => {
if (userData) {
this.userService.setUser(userData);
this.userService.SignOutFlag = false;
}
}).subscribe(()=>{
console.log('I managed to login');
this.router.navigate(['/dashboard']);
});
}
}
Having loginAfterSignout() as:
loginAfterSignout(): Observable<IUserInfo> {
const httpClientTarget = `${this._ApiUrl}Auth/GetUser?api-version=1`;
return this.httpClient.get<IUserInfo>(httpClientTarget, { headers: this.httpClientHeaders, withCredentials: true })
.do(data => console.log('GetUer returned ' + JSON.stringify(data)))
.catch(this.handleError);
}
Also I have a WinAuthInterceptor as follows:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpInterceptor, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { UserService } from '../services/auth/user.service';
@Injectable()
export class WinAuthInterceptor implements HttpInterceptor {
constructor(public userService: UserService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.userService.SignOutFlag){
req = req.clone({
withCredentials: true
});
}
return next.handle(req);
}
}
Notice in above I do not set the credentials flag if the user has signedOut so the user will not be able to access the API unless he logins back. The key problem is that I could not exactly achieve what I wanted without making changes to my Internet Settings
Internet Options > Security Settings > Local Intranet Zone > User Authentication :
By default I had "Automatic logon only in Intranet zone"
Above meant when I try to sign back into the site I get automatically signed backed into the site [without being shown the browser login window]
To be prompted I had to change settings to "Prompt for user name and password" and restart the browser. Changing of the User Authentication/ Logon setting is not something in my control.
Narrowed down version of my problem is: How would I trigger displaying the Windows built in login window on demand without changing my intranet security settings? This is something I could not accomplish in above setup.