Я создаю приложение, которое использует перехватчик Http для аутентификации в Angular и промежуточное программное обеспечение аутентификации в ExpressJS. Промежуточное программное обеспечение перехватчика и аутентификации работает, когда я перехожу к маршруту через приложение, но не когда я нажимаю на него напрямую через URL. Когда я попадаю на маршрут напрямую, я получаю ответ «недопустимое разрешение» от сервера.
Я хотел, чтобы промежуточное программное обеспечение аутентификации применялось к нескольким маршрутам, поэтому я написал его перед маршрутами, которые я хочу аутентифицировать.
router.use(express.static(path.join(__dirname, distDir)));
router.use('/auth', require('./auth/authRoutes'));
router.use([auth.checkTokens, auth.verifyTokens.verifyUserToken]);
router.use('/users', require('./user/userRoutes'));
router.use('/courses', require('./course/courseRoutes'));
router.get('/*', (req, res, next) => {
res.sendFile(path.resolve(__dirname, distDir + '/index.html'));
})
Мой метод проверки токенов ищет токен, который был передан в заголовке:
exports.checkTokens = (req, res, next) => {
const userToken = req.headers['authorization'];
if (userToken) {
req.access_token = userToken;
next();
} else {
return res.status(400).send({ auth: false, error: 'Invalid grant' });
}
}
Токен хранится в локальном хранилище, которое затем присоединяется к перехватчику Http до того, как запрос будет отправлен из внешнего интерфейса.
Это правильная реализация? Я хотел использовать локальное хранилище, чтобы легко проверить функциональность моего перехватчика. Разрешит ли сохранение токена в cookie мою конкретную проблему?
- ОБНОВЛЕНИЕ -
Маршрут, по которому я пытаюсь ориентироваться, это «API / курсы». Я могу заставить аутентификацию работать, когда я нажимаю на ссылку через приложение, но не когда я набираю адрес прямо в адресной строке URL. Я хочу иметь возможность аутентифицировать пользователя, даже если он вводит URL-адрес в адресную строку напрямую.
Вот мой компонент курсов:
@Component({
selector: 'app-courses',
templateUrl: './courses.component.html',
styleUrls: ['./courses.component.less']
})
export class CoursesComponent extends HttpService implements OnInit {
courses: Course[];
constructor(private http: HttpClient,
httpErrorHandler: HttpErrorHandler) {
super(http, httpErrorHandler, 'JWTService')
this.courses = [];
}
ngOnInit() {
this.getCourses().subscribe((data: Course[]) => {
this.courses = data;
});
}
private getCourses(): Observable<Course[]> {
return super.get<Course[]>('courses', this.courses, 'get courses')
}
}
И у меня на маршрутах настроен авторский охранник:
export const HomeRoutes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AuthGuard]
},
{
path: 'user-settings',
component: UserSettingsComponent,
canActivate: [AuthGuard]
},
{
path: 'courses',
component: CoursesComponent,
canActivate: [AuthGuard]
},
{
path: 'course-form',
component: CourseFormComponent,
canActivate: [AuthGuard]
}
];
Это мой автор:
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem('access_token')) {
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/auth/login'], { queryParams: {} });
// this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url } });
return false;
}
}
Это мой класс перехватчиков в Angular:
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
private isRefreshingToken = false;
private accessToken = localStorage.getItem('access_token')
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
oldToken = localStorage.getItem('access_token');
constructor(public authService: AuthService, private http: HttpClient) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.addToken(request, this.accessToken);
return next.handle(request).pipe(
catchError(error => {
console.log(error)
if (error instanceof HttpErrorResponse) {
switch((<HttpErrorResponse>error).status) {
case 400:
// console.log('handling 400 error')
return this.handle400Error(error);
case 401:
// console.log('handling 401 error')
this.authService.refresh().subscribe((data: { access_token: string, refresh_token: string}) => {
localStorage.setItem('access_token', data.access_token)
localStorage.setItem('refresh_token', data.refresh_token)
})
return this.handle401Error(request, next);
}
} else {
return throwError(error);
}
})
);
}
private handle400Error(error: HttpErrorResponse): Observable<HttpEvent<any>> {
if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
// If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
return this.logoutUser();
}
return throwError(error);
}
private logoutUser() {
this.authService.logout()
return throwError('Error. Logged out user.')
}
private handle401Error(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);
return this.authService.refreshToken().pipe(
switchMap((newToken: string) => {
newToken = localStorage.getItem('access_token');
if (newToken) {
this.tokenSubject.next(newToken);
if (this.oldToken === newToken) {
return this.logoutUser();
} else {
return next.handle(this.addToken(req, newToken));
}
}
// If we don't get a new token, we are in trouble so logout.
return this.logoutUser();
}),
catchError(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
return this.logoutUser();
}),
finalize(() => {
this.isRefreshingToken = false;
})
);
} else {
return this.tokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(token => {
return next.handle(this.addToken(req, token));
})
)
}
}
private addToken(request: HttpRequest<any>, access_token: string): HttpRequest<any> {
return request.clone({
setHeaders: {
Authorization: `Bearer: ${access_token}`
}
});
}
}