Angular 2 - Cannot instantiate cyclic dependency - javascript

I create custom XHRBackend class to catch 401 error globally. In AuthService I have 2 methods which use http - login and refreshToken. So i have dependency chain like that: Http -> customXHRBackend -> AuthService -> Http. How can I fix this?
export class CustomXHRBackend extends XHRBackend {
constructor(browserXHR: BrowserXhr,
baseResponseOptions: ResponseOptions,
xsrfStrategy: XSRFStrategy,
private router: Router,
private authService: AuthService) {
super(browserXHR, baseResponseOptions, xsrfStrategy);
}
createConnection(request: Request): XHRConnection {
let connection: XHRConnection = super.createConnection(request);
connection.response = connection.response
.catch(this.handleError.bind(this));
return connection;
}
handleError(error: Response | any) {
console.log('ERROR',error['status']);
if(error['status'] === 401) {
this.authService.logout();
this.router.navigate(['/']);
}
return Observable.throw(error);
}
}
AuthService.ts
#Injectable()
export class AuthService {
private loggedIn: boolean = false;
constructor(private http: Http) {
this.loggedIn = !!localStorage.getItem('authToken');
}
login(email: string, password: string): Observable<Response> {
let headers: Headers = new Headers();
headers.set('Content-Type', 'application/json');
return this.http.post('https://httpbin.org/post',
{
email: email,
password: password
},
{
headers: headers
})
.map((response) => {
let res = response.json();
// if (res['success']) {
if (res) {
localStorage.setItem('authToken', res['token']);
localStorage.setItem('refreshToken', res['refreshToken']);
console.log('logged');
this.loggedIn = true;
}
return response;
}
);
}
logout(): void {
localStorage.removeItem('authToken');
this.loggedIn = false;
console.log('Logged out');
}
isLogged(): boolean {
return this.loggedIn;
}
refreshToken(): Observable<Response> {
let headers: Headers = new Headers();
headers.set('token', localStorage.getItem('token'));
headers.set('refreshToken', localStorage.getItem('refreshToken'));
return this.http.get('https://httpbin.org/get', {
headers: headers
});
}
}
Include CustomXHRBackend in app.module.ts
{
provide: XHRBackend,
useFactory: (browserXHR: BrowserXhr,
baseResponseOptions: ResponseOptions,
xsrfStrategy: XSRFStrategy,
router: Router,
authService: AuthService) => {
return new CustomXHRBackend(browserXHR, baseResponseOptions, xsrfStrategy, router, authService);
},
deps: [BrowserXhr, ResponseOptions, XSRFStrategy, Router, AuthService]
}

How about HTTP Interceptors... There's a blog post here.
If you Google you'll find more...
Here's how you hook one into you App Module
you can clone the request in you interceptor and add X-CustomAuthHeader into headers etc.

Please see in your constructor where you inject dependency. You can't inject in a few Services the same dependency.Example: CustomXHRBackend => AuthService, AuthService => CustomXHRBackend

Related

CRUD : update method doesn't work [Angular]

I faced an issue with update method. I'm working on a spring boot angular project so update doesnt work on frontend my code looking logic can someone gie me an idea about this issue
user.service
updateProfile(userData: SignUpData, id: string ): Observable<any> {
return this.http.patch( API_URL + 'update/' + id, userData, httpOptions);
}
component.ts
form: any = {};
id: string;
errorMessage = '';
currentUser: any;
constructor(private userservice: UserService, private route: ActivatedRoute, private router: Router, private token: TokenStorageService) { }
ngOnInit() {
this.currentUser = this.token.getUser();
}
onSubmit() {
const {adresse1, ...rest} = this.form;
const userData: SignUpData = {...rest, adresses: [adresse1]};
this.userservice.updateProfile(userData, this.currentUser.id).subscribe(
data => {
console.log(data);
},
err => {
this.errorMessage = err.error.message;
}
);
}
Interceptor.ts
export class AuthInterceptor implements HttpInterceptor {
constructor(private token: TokenStorageService) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
let authReq = req;
const token = this.token.getToken();
if (token != null) {
authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
}
return next.handle(authReq);
}
}
export const authInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
];
I try to logout & log in and that's working because first I didn't get user id so I add this.currentUser = this.token.getUser(); without refresh authenticate so GET was always returning 401 not found. I hope this answer can help people that have the same issue And thank you guys for your replies

User is Null when using a Service over UserManager

Using Angular 7 and OIDC-Client I created an AuthService that exposes a few UserManager methods.
When I call signInRedirectCallback from the AuthService the user is null.
If I call it using UserManager directly the user is not null.
More details below:
const settings : UserManagerSettings = {
authority: 'https://localhost:5005',
client_id: 'spa',
redirect_uri: 'https://localhost:5001/signin',
post_logout_redirect_uri: 'https://localhost:5001/signout',
response_mode: 'query',
response_type: 'code',
scope: 'openid profile email offline_access api',
filterProtocolClaims: true,
loadUserInfo: true
};
#Injectable({
providedIn: 'root'
})
export class AuthService {
private manager = new UserManager(settings);
private user: User = null;
constructor() {
this.manager.getUser().then(user => {
this.user = user;
});
}
isSignedIn(): boolean {
return this.user != null && !this.user.expired;
}
getClaims(): any {
return this.user.profile;
}
signInRedirect(args?: any): Promise<void> {
return this.manager.signinRedirect(args);
}
signInRedirectCallback(url?: string): Promise<void> {
return this.manager.signinRedirectCallback(url).then(user => {
this.user = user;
});
}
// Other methods
}
I have an AuthenticateGuard as follows:
export class AuthenticatedGuard implements CanActivate {
constructor(private authService: AuthService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) : boolean {
if (this.authService.isSignedIn())
return true;
this.authService.signInRedirect({ state: { url: state.url }});
return false;
}
}
Then my callback component is the following:
import { Component, OnInit } from '#angular/core';
import { AuthService } from '../shared/services/auth.service'
import { UserManagerSettings, UserManager } from 'oidc-client';
import { Router, ActivatedRoute } from '#angular/router';
#Component({
selector: 'signin',
templateUrl: './signin.component.html'
})
export class SignInComponent implements OnInit {
private manager = new UserManager(settings);
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
this.manager.signinRedirectCallback().then(function (user) {
console.log(user);
});
}
}
On the callback component user is undefined when I use console.log(user).
To make this work I needed to create a new UserManager on the SignInComponent (Callback Component) instead of using the AuthService, e.g.:
const settings : UserManagerSettings = {
authority: 'https://localhost:5005',
client_id: 'spa',
redirect_uri: 'https://localhost:5001/signin',
post_logout_redirect_uri: 'https://localhost:5001/signout',
response_mode: 'query',
response_type: 'code',
scope: 'openid profile email offline_access api',
filterProtocolClaims: true,
loadUserInfo: true
};
#Component({
selector: 'signin',
templateUrl: './signin.component.html'
})
export class SignInComponent implements OnInit {
private manager = new UserManager(settings);
constructor(private router: Router) { }
ngOnInit() {
this.manager.signinRedirectCallback().then(function (user) {
console.log(user);
});
}
}
Any idea why this happens? What am I missing?
Thank You
Few thing to try
Change your response_type to this
response_type: 'id_token token'
and remove null in user
private user: User;
and remove these 2
response_mode
#Injectable({
providedIn: 'root'
})
2 things to check:
First, open the browser's console and see if the signinRedirectCallback() call is throwing any errors.
Second, look in the application's session storage, do you see any user data populated by the authentication flow?
Another observation: you should not create instances of UserManager for every page; All the logic for oauth with oidc-client should be encapsulated in
a service that will be injected in the different pages of the application.
Change the isSignedIn method to promise-based.
isSignedIn(): Promise<boolean> {
return new Promise((resolve, reject) => {
this.manager.getUser().then(user => {
resolve(user != null && !user.expired);
});
});
}
And your route guard:
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
this.authService.isLoggedIn().then(result => {
if (result) {
resolve(result);
} else {
this.authService.startAuthentication().finally(() => {
reject(false);
});
}
});
});
}

JWT expired token handling with Angular Interceptor keeps refreshing

I have an interceptor in Angular that I am using to refresh a token if it is expired, but the application seems to get caught in an endless call of 401 errors to the API when the token is successfully refreshed. When I step through the code, the token does indeed refresh if expired but then tries to refresh repeatedly.
I should also note that upon clicking the button again and calling the API again, the app picks up the new token and works properly afterward. Would love to get this working without so many console errors in the first place though.
Here is the interceptor (old)
import { Injectable, Injector } from "#angular/core";
import { Router } from "#angular/router";
import {
HttpClient,
HttpHandler, HttpEvent, HttpInterceptor,
HttpRequest, HttpResponse, HttpErrorResponse
} from "#angular/common/http";
import { AuthService } from "./auth.service";
import { Observable } from "rxjs/Observable";
#Injectable()
export class AuthResponseInterceptor implements HttpInterceptor {
currentRequest: HttpRequest<any>;
auth: AuthService;
constructor(
private injector: Injector,
private router: Router
) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.injector.get(AuthService);
var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
if (token) {
// save current request
this.currentRequest = request;
return next.handle(request)
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// do nothing
}
})
.catch(error => {
return this.handleError(error)
});
}
else {
return next.handle(request);
}
}
handleError(err: any) {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// JWT token might be expired:
// try to get a new one using refresh token
console.log("Token expired. Attempting refresh...");
this.auth.refreshToken()
.subscribe(res => {
if (res) {
// refresh token successful
console.log("refresh token successful");
// re-submit the failed request
var http = this.injector.get(HttpClient);
http.request(this.currentRequest).subscribe(
(result: any) => {
console.log(this.currentRequest);
}, (error: any) => console.error(error)
);
}
else {
// refresh token failed
console.log("refresh token failed");
// erase current token
this.auth.logout();
// redirect to login page
this.router.navigate(["login"]);
}
}, error => console.log(error));
}
}
return Observable.throw(err);
}
}
EDIT:
Updated code to working solution
import { Injectable, Injector } from "#angular/core";
import { Router } from "#angular/router";
import {
HttpClient,
HttpHandler, HttpEvent, HttpInterceptor,
HttpRequest, HttpResponse, HttpErrorResponse, HttpHeaders
} from "#angular/common/http";
import { AuthService } from "./auth.service";
import { Observable, Subject } from "rxjs";
#Injectable()
export class AuthResponseInterceptor implements HttpInterceptor {
auth: AuthService;
currentRequest: HttpRequest<any>;
constructor(
private injector: Injector,
private router: Router
) { }
logout() {
this.auth.logout();
this.router.navigate(["login"]);
}
intercept(
request: HttpRequest<any>,
next: HttpHandler): Observable<HttpEvent<any>> {
this.auth = this.injector.get(AuthService);
let token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
this.currentRequest = request;
return next.handle(request).
catch((error) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.auth.refreshToken()
.switchMap(() => {
let token = (Response) ? this.auth.getAuth() : null;
console.log(token);
if (token) {
this.currentRequest = request.clone({
setHeaders: {
Authorization: `Bearer ${token.token}`
}
});
}
return next.handle(this.currentRequest);
}).
catch((e) => {
this.logout();
console.error(e);
return Observable.empty();
});
}
return Observable.throw(error);
});
}
}
Auth.service
constructor(private http: HttpClient,
#Inject(PLATFORM_ID) private platformId: any) {
}
// performs the login
login(username: string, password: string): Observable<boolean> {
var url = "api/token/auth";
var data = {
username: username,
password: password,
client_id: this.clientId,
// required when signing up with username/password
grant_type: "password",
// space-separated list of scopes for which the token is issued
scope: "offline_access profile email"
};
return this.getAuthFromServer(url, data);
}
// try to refresh token
refreshToken(): Observable<boolean> {
var url = "api/token/auth";
var data = {
client_id: this.clientId,
// required when signing up with username/password
grant_type: "refresh_token",
refresh_token: this.getAuth()!.refresh_token,
// space-separated list of scopes for which the token is issued
scope: "offline_access profile email"
};
return this.getAuthFromServer(url, data);
}
// retrieve the access & refresh tokens from the server
getAuthFromServer(url: string, data: any): Observable<boolean> {
return this.http.post<TokenResponse>(url, data)
.map((res) => {
let token = res && res.token;
// if the token is there, login has been successful
if (token) {
// store username and jwt token
this.setAuth(res);
// successful login
return true;
}
// failed login
return Observable.throw('Unauthorized');
})
.catch(error => {
return new Observable<any>(error);
});
}
// performs the logout
logout(): boolean {
this.setAuth(null);
return true;
}
// Persist auth into localStorage or removes it if a NULL argument is given
setAuth(auth: TokenResponse | null): boolean {
if (isPlatformBrowser(this.platformId)) {
if (auth) {
localStorage.setItem(
this.authKey,
JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
}
return true;
}
// Retrieves the auth JSON object (or NULL if none)
getAuth(): TokenResponse | null {
if (isPlatformBrowser(this.platformId)) {
var i = localStorage.getItem(this.authKey);
if (i) {
return JSON.parse(i);
}
}
return null;
}
// Returns TRUE if the user is logged in, FALSE otherwise.
isLoggedIn(): boolean {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(this.authKey) != null;
}
return false;
}
return this.auth.refreshToken(response:any)
//response can be true or null
let token=(response)?this.auth.getAuth():null;
//In token we have an object of type TokenResponse
console.log(token)
.switchMap(() => {
if (token) {
this.currentRequest = request.clone({
setHeaders: { //I think it's toke.token
Authorization: `Bearer ${token.token}`
}
});
....
NOTE: Try to change "var" for "let"
NOTE2: At first you have
var token = (this.auth.isLoggedIn()) ? this.auth.getAuth()!.token : null;
// May be remove "!"?
let token = (this.auth.isLoggedIn()) ? this.auth.getAuth().token : null;
If you want separate the error handle you can do some like
handleError(err: any) {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
this.auth.refreshToken()
.switchMap(res=>{ //<--switchMap, not susbcribe
if (res) {
console.log("refresh token successful");
// re-submit the failed request
var http = this.injector.get(HttpClient);
//Modify there this.currentRequest if was neccesary
return next.handle(this.currentRequest).catch(error:any=>
{
console.error(error);
return Observable.throw(error);
});
}
else {
console.log("refresh token failed");
this.auth.logout();
this.router.navigate(["login"]);
}
})
}
}
return Observable.throw(err);
}

Angular2: oauth2 with token headers

I'm new to angular2. In 1.* everything was fine with interceptors, just add them: and you have everywhere your headers, and you can handle your requests, when token became invalid...
In angular2 i'm using RxJs.
So i get my token:
getToken(login: string, pwd: string): Observable<boolean> {
let bodyParams = {
grant_type: 'password',
client_id: 'admin',
scope: AppConst.CLIENT_SCOPE,
username: login,
password: pwd
};
let params = new URLSearchParams();
for (let key in bodyParams) {
params.set(key, bodyParams[key])
}
let headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
let options = new RequestOptions({headers: headers});
return this.http.post(AppConst.IDENTITY_BASE_URI + '/connect/token', params.toString(), options)
.map((response: Response) => {
let data = response.json();
if (data) {
this.data = data;
localStorage.setItem('auth', JSON.stringify({
access_token: data.access_token,
refresh_token: data.refresh_token
}));
return true;
} else {
return false;
}
});
}
and then how can i use this token in every request? i don't want to set .header in every request. It's a bad practice.
And then: for example when i do any request, and get 401-error, how can i intercept, and get a new token, and then resume all requests, like it was in angular 1?
i tried to use JWT from here jwt, but it doesn't meet my requirements, btw in first angular i was using Restangular - and everything was fine there (also with manual on tokens:https://github.com/mgonto/restangular#seterrorinterceptor)
You can either extend the default http service and use the extended version, or you could create a method that gets some parameters (if necessary) and return a RequestOptions objects to pass default http service.
Option 1
You can create a service:
#Injectable()
export class HttpUtils {
constructor(private _cookieService: CookieService) { }
public optionsWithAuth(method: RequestMethod, searchParams?: URLSearchParams): RequestOptionsArgs {
let headers = new Headers();
let token = 'fancyToken';
if (token) {
headers.append('Auth', token);
}
return this.options(method, searchParams, headers);
}
public options(method: RequestMethod, searchParams?: URLSearchParams, header?: Headers): RequestOptionsArgs {
let headers = header || new Headers();
if (!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
let options = new RequestOptions({headers: headers});
if (method === RequestMethod.Get || method === RequestMethod.Delete) {
options.body = '';
}
if (searchParams) {
options.params = searchParams;
}
return options;
}
public handleError(error: Response) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
Usage example:
this._http
.get('/api/customers', this._httpUtils.optionsWithAuth(RequestMethod.Get))
.map(res => <Customer[]>res.json())
.catch(err => this._httpUtils.handleError(err));
This example is using cookies to store and access the token. You could use a parameter as well.
Option 2
Second option is to extend http service, for example like this:
import { Injectable } from '#angular/core';
import { Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class MyHttp extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = 'fancyToken';
options.headers.set('Auth', token);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = 'fancyToken';
if (typeof url === 'string') {
if (!options) {
options = {headers: new Headers()};
}
options.headers.append('Auth', token);
} else {
url.headers.append('Auth', token);
}
return super.request(url, options).catch(this.handleError(this));
}
private handleError (self: MyHttp) {
return (res: Response) => {
if (res.status === 401) {
// do something
}
return Observable.throw(res);
};
}
}
And in your #NgModule:
#NgModule({
// other stuff ...
providers: [
{
provide: MyHttp,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new MyHttp(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
]
// a little bit more other stuff ...
})
Usage:
#Injectable()
class CustomerService {
constructor(private _http: MyHttp) {
}
query(): Observable<Customer[]> {
return this._http
.get('/api/customers')
.map(res => <Customer[]>res.json())
.catch(err => console.log('error', err));
}
}
Extra:
If you want to use refresh token to obtain a new token you can do something like this:
private handleError (self: MyHttp, url?: string|Request, options?: RequestOptionsArgs) {
return (res: Response) => {
if (res.status === 401 || res.status === 403) {
let refreshToken:string = 'fancyRefreshToken';
let body:any = JSON.stringify({refreshToken: refreshToken});
return super.post('/api/token/refresh', body)
.map(res => {
// set new token
})
.catch(err => Observable.throw(err))
.subscribe(res => this.request(url, options), err => Observable.throw(err));
}
return Observable.throw(res);
};
}
To be honest, I haven't tested this, but it could provide you at least a starting point.
We solved the issue with extension of AuthHttp. We added a method a on AuthHttp to set a new header dynamically like that (X-RoleId is a custom header)
declare module 'angular2-jwt' {
interface AuthHttp {
setRoleId(config: {});
}
}
AuthHttp.prototype.setRoleId = function (roleId) {
let jsThis = <any>(this);
jsThis.config.globalHeaders = [
{'Content-Type': 'application/json'},
{'X-RoleId': roleId}
];
};

Value become null for this in Promise callback

I am using the following code value of this become null when i call it inside the then function here is the code. Am i doing something wrong or it is like this or there is any work around to resolve this issue
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { CanActivate, Router } from '#angular/router';
import { AuthService } from '../services/auth.service';
import { WebAPISettings } from '../services/webapisettings.service';
#Injectable()
export class LoginService {
//_ngWEBAPISettings: WebAPISettings;
//_authService: AuthService;
constructor(private http: Http, private ngWEBAPISettings: WebAPISettings, private authService: AuthService) {
//this._ngWEBAPISettings = ngWEBAPISettings;
//this._authService = authService;
}
public login(username: string, password: string): Promise<any> {
let data = "grant_type=password&username=" + username + "&password=" + password;
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
try {
debugger;
return this.http.post(this.ngWEBAPISettings.apiServiceBaseUri + "token", data, options)
.toPromise()
.then(function (res: Response) {
debugger;
let body = res.json();
//let _authService: AuthService = new AuthService();
this.authService.fillAuthDataFromLogin(body);
//this.router.navigate(['/Home']);
return body.data || {};
})
.catch(this.handleError);
}
catch (error) {
console.log(error);
}
}
private extractData() {
}
private handleError(error: any) {
debugger;
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
and i am debugging it in the chrome here is the screenshot please help me in fixing it.
after using the arrow function same thing check the screen shot
one thing to mention i am using Angular2 RC4.
You could use an arrow function to be able to use the lexical this:
return this.http.post(this.ngWEBAPISettings.apiServiceBaseUri + "token", data, options)
.toPromise()
.then((res: Response) => { // <-----
(...)
});
This way, this will correspond to the instance of the LoginService service.
See this doc for more details:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Categories

Resources