Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do you perform logout #4

Open
majhoos opened this issue Feb 25, 2018 · 2 comments
Open

How do you perform logout #4

majhoos opened this issue Feb 25, 2018 · 2 comments

Comments

@majhoos
Copy link

majhoos commented Feb 25, 2018

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()

@DeBiese
Copy link
Owner

DeBiese commented Apr 6, 2018

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):

  1. 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.

  2. 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.

@majhoos
Copy link
Author

majhoos commented Apr 6, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants