i am currently implementing an angular2 example application with spring boot as backend. I am having some problems with the frontend auth guard mechanism and observables.
I am trying to achieve:
when someone enters a protected route the auth guard should check if a user
is already set in the auth service variable
if it is not set then a http request should be issued to check if a session is available
the service method should return a true/false value (asynchronously because of the possible http request)
if service returns false the auth guard should redirect to login page
auth guard should return true/false so the route can either be activated or not
My code currently looks like this (i am using RC5 btw.):
Auth Guard
import {Injectable} from "#angular/core";
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from "#angular/router";
import {Observable, Subject} from "rxjs/Rx";
import {AuthService} from "./auth.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
var authenticated = this.authService.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
console.log("onNext guard: "+res);
if(!res && state.url !== '/signin') {
console.log("redirecting to signin")
this.router.navigate(['/signin']);
}
subject.next(res);
});
return subject.asObservable();
}
}
Auth Service
import {Injectable} from "#angular/core";
import {User} from "./user.interface";
import {Router} from "#angular/router";
import {Http, Response, Headers} from "#angular/http";
import {environment} from "../environments/environment";
import {Observable, Observer, Subject} from "rxjs/Rx";
#Injectable()
export class AuthService {
private authenticatedUser : User;
constructor(private router: Router, private http: Http) {}
signupUser(user: User) {
}
logout() {
//do logout stuff
this.router.navigate(['/signin']);
}
isAuthenticated() : Observable<boolean> {
var subject = new Subject<boolean>();
if (this.authenticatedUser) {
subject.next(true);
} else {
this.http.get(environment.baseUrl + '/user')
.map((res : Response) => res.json())
.subscribe(res => {
console.log("next: returning true");
this.authenticatedUser = User.ofJson(res);
subject.next(true);
}, (res) => {
console.log("next: returning false");
subject.next(false);
});
}
return subject.asObservable();
}
}
The problem is: the guard never allows the router component to activate, even though when i am logged in.
Thanks for the help!
Change
return subject.asObservable();
to
return subject.asObservable().first();
The router waits for the observable to complete. first() makes it complete after the first event.
Related
I've managed to get JWT authentication in my nestJS application.
Now I want to implement the role guard and have therefore to check the role of the authenticated user.
Therefore, I thought of requesting the respective user role from the database. But this call is async and this is not doable within the guard.
My question is:
How can I get the user role information within the Guard?
I could put the information in the JWT token, but this seems not right to me, or am I wrong?
Here, Implementing Passport JWT you can put your findUser in the validate function that is async. And then create a decorator to return the user Auth JWT in decorator in NESTJS
So you need to do some things like this
//jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { jwtConstants } from './constants';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
// Your JWT payload
// Insert here the findOne from you're BDD
return { userId: payload.sub, username: payload.username };
}
}
And then
//user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator((data: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
And in your controller juste use
//user.controller.ts
import { User } from './user.decorator';
#Get()
async getUser(#User() user) {
//console.log(user);
}
I'm building a GraphQL API using Nest framework and I'm trying to implement 3rd party express middlewares (express-rate-limit and express-slow-down) into some queries and mutations.
The problem is all graphql mutations and queries use the same endpoint, so I can't explicitly tell to which query or mutations shall the middleware be applied, because you can only do that using route's path (which is the same across the API).
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '#nestjs/common'
import * as rateLimit from 'express-rate-limit'
import * as RedisStore from 'rate-limit-redis'
import { RedisClient } from 'redis'
#Module({
providers: [],
exports: [],
})
export default class SecurityModule implements NestModule
{
constructor(protected readonly redisClient: RedisClient)
{
}
configure(consumer: MiddlewareConsumer)
{
consumer.apply(
new rateLimit({
max: 300,
windowMs: 15 * 60 * 1000,
store: new RedisStore({ client: this.redisClient }),
})).forRoutes({ path: '/graphql', method: RequestMethod.ALL }) // this would apply the middleware to all queries and mutations
}
}
So I tried using both guards and interceptors for that purpose, but failed miserably.
It's a fail for an obvious reason.
The Error: Can't set headers after they are sent is thrown.
/* !!! My Interceptor would like quite identical */
import { ExecutionContext, Injectable, CanActivate } from '#nestjs/common'
import * as speedLimit from 'express-slow-down'
import { Request, Response } from 'express'
#Injectable()
export default class SpeedLimitGuard implements CanActivate
{
constructor(
protected readonly options: speedLimit.Options,
) {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { req, res }: { req: Request, res: Response } = context.getArgs()[2]
speedLimit({ ...this.options })(req, res, req.next)
return true
}
}
import { NestInterceptor, ExecutionContext, Injectable, INestApplication, INestExpressApplication } from '#nestjs/common'
import { Observable } from 'rxjs'
import * as speedLimit from 'express-slow-down'
// import { Request, Response } from 'express'
import { ApplicationReferenceHost } from '#nestjs/core'
import { RedisClient } from 'redis'
import * as RedisStore from 'rate-limit-redis'
#Injectable()
export default class SpeedLimitInterceptor implements NestInterceptor
{
constructor(private readonly appRefHost: ApplicationReferenceHost,
private readonly redisClient: RedisClient, )
{}
intercept<T>(context: ExecutionContext, call$: Observable<T>): Observable<T>
{
// const { req: request, res: response }: { req: Request, res: Response } = context.getArgs()[2]
const httpServer = this.appRefHost.applicationRef
const app: INestApplication & INestExpressApplication = httpServer.getInstance()
app.use(speedLimit({
delayAfter: 1,
store: new RedisStore({
prefix: 'test_',
client: this.redisClient,
}),
}))
app.use((req, res, next) => {
console.log('is middleware triggered', { req, res })
next()
})
return call$
}
}
Is there any way to apply a 3rd party express middleware to a GraphQL Mutation/Query explicitly?
So from the bottom, guards are working, because I'm the living human bean that can prove it:
#Query('getHome')
#UseGuards(GraphqlGuard)
async findOneById(#Args('id') id: string): Promise<HomeEntity> {
return await this.homeService.findOneById(id);
}
and it's just working.
This is GraphqlGuard.ts
import {ExecutionContext, Injectable} from '#nestjs/common';
import {GqlExecutionContext} from '#nestjs/graphql';
import {AuthGuard} from '#nestjs/passport';
import {ExecutionContextHost} from '#nestjs/core/helpers/execution-context.host';
import {Observable} from 'rxjs';
#Injectable()
export class GraphqlGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const ctx = GqlExecutionContext.create(context);
const {req} = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
}
But to live with context, you have to make it works for you, so, wherever you're passing graphql config, there is an context callback, and for me it looks like this:
context: (context) => {
let req = context.req;
if (context.connection) {
req = context.connection.context.req;
}
return {req};
}
I'm checking here connection for context from websocket. Im using global interceptors so, they're working like a charm. But you still can use #UseInterceptors(SomeInterceptor) decorator and it also works. And btw Middlewares, at the end, I doesn't need any of them guards, pipes, validators and interceptors for me was quite enough.
Regards.
I am trying to make a post request in my Angular 2 application. I am getting an error. I have a CORS plugin enabled and when I use Postman there requests are successful so no idea what might be wrong. I would appreciate any help :)
Failed to load resource: the server responded with a status of 404 (Not Found)
16:1 XMLHttpRequest cannot load https://website/api/v1/auth/sign_in. Response for preflight has invalid HTTP status code 404
Service.ts
import { Injectable } from "#angular/core";
import { Http, Headers, Response } from "#angular/http";
import 'rxjs/Rx';
import { Observable } from "rxjs";
import {User} from "./auth.model";
#Injectable()
export class AuthService{
constructor(private http: Http){
}
signUp(user:User){
const body= JSON.stringify(user);
const headers= new Headers ({'Content-type':'application/json'});
return this.http.post('https://website/api/v1/auth', body,{headers:headers})
.map((response:Response)=> response.json())
.catch((error: Response)=>Observable.throw(error.json()));
}
signIn(user:User){
const body= JSON.stringify(user);
const headers= new Headers ({'Content-type':'application/json'});
return this.http.post('https://website/api/v1/auth/sign_in', body,{headers:headers})
.map((response:Response)=>response.json())
.catch((error: Response)=>Observable.throw(error.json()));
}
}
signin.ts
import { Component, OnInit } from '#angular/core';
import {FormGroup, FormControl, Validators,FormsModule} from '#angular/forms';
import {AuthService} from './auth.service';
import {User} from "./auth.model";
#Component({
selector: 'app-signin',
templateUrl: './signin.component.html',
styleUrls: ['./signin.component.css']
})
export class SigninComponent implements OnInit {
myForm: FormGroup;
constructor(private authService: AuthService) { }
onSubmit(){
const user= new User(this.myForm.value.email, this.myForm.value.password);
this.authService.signIn(user).subscribe();
console.log("c");
}
ngOnInit() {
this.myForm= new FormGroup({
email: new FormControl(null, [Validators.required,
Validators.pattern("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
]),
password: new FormControl(null, [Validators.required])
});
}
}
I believe the third argument passed to the http.post method is a RequestOptions object
const headers= new Headers ({'Content-type':'application/json'});
const options = new RequestOptions({ headers });
this.http.post('https://website/api/v1/auth', user, options)
Also you should not need to stringify you object before sending it just send the user object as the body
You should also check to ensure that the server is returing Access-Control-Allow-Origin in the header
Currently I want to implement canActivate function, everything I want is to send a request to server each time page requested and get true/false in a json response in order to understand is user authenticated and permitted to review current page.
And it seems that I completely stuck with observable and promise objects, which is new for me, what is what I have so far.
import { Injectable } from '#angular/core';
import {CanActivate, Router} from '#angular/router';
import { Http, Response } from '#angular/http';
import {Observable, Observer, Subject} from "rxjs/Rx";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private http: Http) {}
canActivate() {
if (this.isAuthenticated()) {
return true;
} {
this.router.navigate(['404']);
return false;
}
}
isAuthenticated() : Observable<boolean> {
var subject = new Subject<boolean>();
this.http.get("/index.php?module=Api&action=IsAuthenticated")
.map((res : Response) => res.json())
.subscribe(res => {
console.log("next: returning true");
subject.next(true);
}, (res) => {
console.log("next: returning false");
subject.next(false);
});
return subject.asObservable().first();
}
}
A few changes
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private http: Http) {}
canActivate() {
return this.isAuthenticated().first(); // not sure if `.first() is still necessary
}
isAuthenticated() : Observable<boolean> {
return this.http.get("/index.php?module=Api&action=IsAuthenticated")
.map((res : Response) => res.json())
.catch(err => return Observable.of(false))
.map(res => {
return true
});
}
}
If isAuthenticated() does some async execution, we don't get true or false back, we get an Observable that emits a true or false value eventually
What we do is to return the observable we get from isAuthenticated()
In isAuthenticated with return the observable we get fromthis.http.get()and transform the emitted event. It seems the response from the server (res.json()) is not used. Therefore we usecatch()to returnfalsein case of an error andtrue` otherwise.
Because the response from the server is not used .map((res : Response) => res.json()) could be omitted, except this is where you expect an error from that should case false to be returned.
Also your production code might look different and require the response to be processed.
We don't subscribe anywhere, because this is what the router is doing when an Observable is returned from canActivate and if we call subscribe() we get a Subscription instead of an Observable.
canActivate can return either Observable<boolean>, Promise<boolean> or boolean.
As you are depending on asynchronous checking you cannot return a boolean.
It however looks like you could just simply do
canActivate(): Observable<boolean> {
return this.isAuthenticated();
}
I'm no expert on Observable yet, but it would also be easy for you to chain on a call to redirect if you where not authorised.
Here is the solution that works for me:
canActivate() {
return this.http.get("/index.php?module=Api&action=IsAuthenticated")
.toPromise()
.then(this.extractData)
.catch(this.handleError);
}
i am developing an application using Angular2 (actually, ionic framework). Application itself contains a login form that should work via API.
Structure:
form.ts
Contains logic related to login form
import {Component, Injectable} from '#angular/core';
import { Http } from '#angular/http';
import {User} from '../../../base/user';
import {API} from '../../../base/API';
#Component({
templateUrl: 'build/pages/account/loginForm/form.html',
selector: 'login-form',
providers: [Http, API]
})
export class AccountForm {
constructor() {
...
}
submitLogin($event) {
var username = this.username.value;
var password = this.password.value;
this.user = new User(username, password);
this.user.authenticate()
}
}
user.ts
Class that contains some user information (username, password hash)
import { Inject } from '#angular/core';
import { Hash } from "./hash";
import { API } from "./API";
export class User {
public username: string;
public password: string;
public pwdhash: string;
constructor(
username: string,
password: string,
private api: API ///// problem is here
) {
let hash = new Hash();
this.username = username;
this.password = password;
this.pwdhash = hash.hash(username, password);
}
authenticate() {
var promise = this.api.loginUser({username: this.username, pwdhash: this.pwdhash});
console.log(promise);
}
}
API.ts
Class with API request to the backed
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Injectable, Inject } from '#angular/core';
import { Hash } from './hash';
#Injectable()
export class API {
private http: Http;
constructor(#Inject(Http) http: Http) {
}
loginUser (user) {
return this.http.post(url, body, options);
}
}
What i want to do is use API class in a User.authenticate() call, but i don't want to pass it as a parameter to the User.constructor(), as this is a bad style.
Another option is to create a User.API member in a constructor, which looks ugly too.
Looks like i need just a function that makes API requests. However, i am not sure this is a good idea for Object-Oriented Programming.
Is there any way to inject API into User class without creating a new API object of this class?
Thanks