Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 - How to best handle expired authentication token?

I am using Angular 2.1.2.

I have an authentication token (using angular2-jwt) and if it expires my webApi call fails with a 401 error. I am looking for a solution where the user will not lose any input data.

I can catch this 401 and open a modal with the login. The user then logs in, the modal goes away, and they see their input screen. However, the failed requests are showing errors so I need to reprocess the requests. If it was a router navigate the initial data has not loaded.

I could reload the page, but if I router.navigate to the same page it does not seem to actually reload the page. I don't want to do a full page reload on the single page app. Is there a way to force router.navigate to run, even if it's the current page?

Re-navigating is still a problem because I would lose any new input data that has not saved.

Ideally, the request would just 'pause' until the user logs in from the modal. I have not found a way to implement this.

Any ideas? Is there a best practice?

like image 274
Don Chambers Avatar asked Nov 04 '16 17:11

Don Chambers


2 Answers

Usually I would provide a HttpService myself instead of using Http directly. So with your requirement, I can provide my own get() method to chain the authentication before sending any real HTTP requests.

Here is the service:

@Injectable()
class HttpService {
  constructor(private http: Http, private auth: Authentication) {}

  public get(url: string): Observable<Response> {
    return this.auth.authenticate().flatMap(authenticated => {
      if (authenticated) {
        return this.http.get(url);
      }
      else {
        return Observable.throw('Unable to re-authenticate');
      }
    });
  }
}

Here is the component to call the service:

@Component({
  selector: 'my-app',
  template: `<h1>Hello {{name}}</h1>
  <button (click)="doSomething()">Do Something</button>

  <div [hidden]="!auth.showModal">
  <p>Do you confirm to log in?</p>
  <button (click)="yes()">Yes</button><button (click)="no()">No</button>
  </div>
  `,
})
export class AppComponent {
   name = 'Angular';

   constructor(private httpSvc: HttpService, public auth: Authentication) {}

   ngOnInit() {
   }

   doSomething() {
     let a = this.httpSvc.get('hello.json').subscribe(() => {
       alert('Data retrieved!');
     }, err => {
       alert(err);
     });
   }

   yes() {
    this.auth.confirm.emit(true);
   }

   no() {
     this.auth.confirm.emit(false);
   }
}

By chaining observables, the Authentication service determines whether to interrupt the normal flow to show the modal (though currently only lives with the App component, it can certainly be implemented separately). And once a positive answer is received from the dialog, the service can resume the flow.

class Authentication {
  public needsAuthentication = true;
  public showModal = false;
  public confirm = new EventEmitter<boolean>();

  public authenticate(): Observable<boolean> {
    // do something to make sure authentication token works correctly
    if (this.needsAuthentication) {
      this.showModal = true;
      return Observable.create(observer => {
        this.confirm.subscribe(r => {
          this.showModal = false;
          this.needsAuthentication = !r; 
          observer.next(r);
          observer.complete();
        });
      });
    }
    else {
      return Observable.of(true);
    }
  }
}

I have a full live example here.

http://plnkr.co/edit/C129guNJvri5hbGZGsHp?open=app%2Fapp.component.ts&p=preview

like image 196
yuxhuang Avatar answered Sep 22 '22 02:09

yuxhuang


Ideally, the request would just 'pause' until the user logs in from the modal. I have not found a way to implement this.

Did you try to switch your button by using tokenNotExpiredattribute like in this example : https://github.com/auth0/angular2-jwt#checking-authentication-to-hideshow-elements-and-handle-routing

It allows you to prevent the 401...

like image 23
Karbos 538 Avatar answered Sep 25 '22 02:09

Karbos 538