How to get user data in Angular after login - javascript

What I want to do is to get the user data and output it anywhere on my website. For example I would like to get the name for the user and output it on the homepage when the user is logged in.
any ideas ? Thanks
AuthService
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { EnvironmentUrlService } from './environment-url.service';
import { UserRegistrationDto } from '../models/user/UserRegistrationDto.model';
import { RegistrationResponseDto } from '../models/user/response/RegistrationResponseDto.model';
import { UserAuthenticationDto } from '../models/user/UserAuthenticationDto.model';
import { AuthResponseDto, user } from '../models/user/response/AuthResponseDto.model';
import { Subject, BehaviorSubject, Observable, map } from 'rxjs';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable({
providedIn: 'root'
})
export class AuthenticationService {
private authChangeSub = new Subject<boolean>()
public authChanged = this.authChangeSub.asObservable();
constructor(private http: HttpClient, private envUrl: EnvironmentUrlService, private jwtHelper: JwtHelperService) {}
public registerUser = (route: string, body: UserRegistrationDto) => {
return this.http.post<RegistrationResponseDto> (this.createCompleteRoute(route, this.envUrl.urlAddress), body);
}
public loginUser = (route: string, body: UserAuthenticationDto) => {
return this.http.post<AuthResponseDto>(this.createCompleteRoute(route, this.envUrl.urlAddress), body);
}
public sendAuthStateChangeNotification = (isAuthenticated: boolean) => {
this.authChangeSub.next(isAuthenticated);
}
public logout = () => {
sessionStorage.removeItem("token");
this.sendAuthStateChangeNotification(false);
}
public isUserAuthenticated = (): boolean => {
const token = sessionStorage.getItem("token");
return token && !this.jwtHelper.isTokenExpired(token);
}
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`;
}
}
login.component.ts
loginUser = (loginFormValue: any) => {
this.showError = false;
const formValues = {... loginFormValue };
const userForAuth: UserAuthenticationDto = {
email: formValues.email,
password: formValues.password
}
this.authService.loginUser('api/accounts/login', userForAuth)
.subscribe({
next: (res:AuthResponseDto) => {
sessionStorage.setItem("token", res.token);
this.authService.sendAuthStateChangeNotification(res.isAuthSuccessful);
this.notificationService.showNotification('success','Login successfully')
this.router.navigate([this.returnUrl]);
},
error: (err: HttpErrorResponse) => {
this.errorMessage = err.message;
this.showError = true;
}})
}
**AuthResponse & User **
export interface AuthResponseDto {
isAuthSuccessful: boolean;
errorMessage: string;
token: string;
}
export interface user {
userId: string;
userName: string
firstName: string;
lastName: string;
role: string []
}
`
I can successfully register and log in a user. I can get the user data from the token but can't map it to user interface

You have to subscribe the api or function user login to get the user information. Also the local storage key value pairing is not used properly here. check user login function, it shows this. set Token(Users[0].

If the data is available in the token received, you can extract it after login and retrieve it with a getter method that return user if user is authenticated, null otherwise (or using promises, that would be cleaner).
The getter method retrieves the data stored in the token of the sessionStorage (if exists) and returns it formatted as user interface. Then you can access the user data fron any component, just import the service in the constructor and call the getter to get the data.

Related

NestJS Interceptor - Append data to incoming request Header or Body

I am trying to modify an NestJS incoming request and append some data either to header or Body. I was able to replace all the body data with my data but i would like to append and not remove the incoming body data.
Here is the code i have
export class MyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
const decoded = jwt_decode(token);
request.body['userId'] = decoded['id'];
}
return next.handle();
}
}
Thanks in advance
I have added two examples as after running testing for the interceptor, it passed without any issue. Of course, my example will be very different to your set up, however, hopefully it'll give you enough insight:
The test file:
test('should not mutate entire request body object', () => {
const dto = {
username: 'testuser',
email: 'test#domain.com',
};
const headers = {
authorization: 'Bearer sdkfjdsakfjdkjfdal',
};
return request(app.getHttpServer())
.post('/')
.send(dto)
.set(headers)
.expect(({ body }) => {
expect(body.userId).toBeDefined();
delete body.userId;
expect(body).toStrictEqual(dto);
});
});
I understand your problem as attempting to obtain information about the authenticated user, and return it/use it later on? However, your current implementation seems to completely override the request.body instead of append your property to the original object.
Interceptor:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.body['userId'] = 'user_123456789';
}
return next.handle();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req): string {
return req.body;
}
}
This returns the correct response and the test will pass. However, you may find a more robust solution would be:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.userId = 'user_123456789';
}
return next.handle();
}
}
And then access this in your controller by:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req) {
return {
userId: req.userId,
...req.body,
};
}
}
Finally, if your only need for an interceptor is to obtain that userId property, you may find that https://docs.nestjs.com/security/authentication#jwt-functionality is useful.
#Injectable()
export class JwtInterceptor implements NestInterceptor {
constructor(private readonly jwtService: JwtService, private readonly
userService: UserService) { }
async intercept(context: ExecutionContext, next: CallHandler):
Promise<Observable<any>> {
var request: WsArgumentsHost = context.switchToWs();
var { handshake: { headers: { authorization } } } =
request.getClient();
try {
var jwt = authorization.split(" ")[1];
var { phone } = await this.jwtService.verify(jwt, jwtConstraints)
var user: User = await this.userService.findUserByPhoneNumber(phone);
request.getData()["user"]=user;
return next.handle().pipe(map((data) => { return { ...data, 'user': "david" }; }));
i hope this will help someone in future while working with socket.i wanted the user object in the body after they pass authentication .the above trick worked out for me

How do I make it so when I create a user it gets added to a 'user' Firestore?

Currently having difficulty figuring out how to add the ability that, when an account is created, the account is added to the Firestore under a collection called 'users'. From what I have seen from other people they added in something like this
.then then(userCredential => {
firestore.collection('users').doc(userCredential.user.uid).set({name})
}
This is my code as it stands after my attempt at it. I am unsure where I should be adding in my code. Currently I have it under this.fireauth.auth.createUserWithEmailAndPassword(this.email, this.password) and the attempt I did is there the program works fine with how it is, I can register an account and see the account in the authentication on the firebase authentication users tab but no 'user' collection is created in the Firestore. I am currently stumped where I should go from here. All I really need is the User-Id/name to be stored in the Firestore.
import { Component, OnInit } from '#angular/core';
import { Platform, AlertController } from '#ionic/angular';
import { LoadingController, ToastController } from '#ionic/angular';
import { Router } from '#angular/router';
import { AngularFireAuth } from '#angular/fire/auth';
import { AngularFirestore } from '#angular/fire/firestore';
#Component({
selector: 'app-register',
templateUrl: './register.page.html',
styleUrls: ['./register.page.scss'],
})
export class RegisterPage {
email: string = '';
password: string = '';
error: string = '';
username: string = '';
image: number;
constructor(private fireauth: AngularFireAuth, private router: Router, private toastController: ToastController, private platform: Platform, public loadingController: LoadingController,
public alertController: AlertController, private firestore: AngularFirestore) {
}
async openLoader() {
const loading = await this.loadingController.create({
message: 'Please Wait ...',
duration: 2000
});
await loading.present();
}
async closeLoading() {
return await this.loadingController.dismiss();
}
signup() {
this.fireauth.auth.createUserWithEmailAndPassword(this.email, this.password)
.then(res => {
if (res.user) {
console.log(res.user);
this.updateProfile();
userCredential => this.firestore.collection('users').doc(userCredential.user.uid).set({
name
})
}
})
.catch(err => {
console.log(`login failed ${err}`);
this.error = err.message;
});
}
updateProfile() {
this.fireauth.auth.onAuthStateChanged((user) => {
if (user) {
console.log(user);
user.updateProfile({
displayName: this.username,
photoURL: `https://picsum.photos/id/${this.image}/200/200`
})
.then(() => {
this.router.navigateByUrl('/home');
})
}
})
}
async presentToast(message, show_button, position, duration) {
const toast = await this.toastController.create({
message: message,
showCloseButton: show_button,
position: position,
duration: duration
});
toast.present();
}
}
This is the current rules that I have set for my database.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
}
}
}
It looks like there is a problem with the arrow function:
userCredential => this.firestore.collection('users').doc(userCredential.user.uid).set({name})
It seems you only initialize function but never call it. Try change this line to just:
this.firestore.collection('users').doc(user.uid).set({name})
or call the anonymous function if you prefer your solution for example:
//... some logic
const initialize = user => this.firestore.collection('users').doc(user.uid).set({name})
initialize(user)

Angular Firebase Error ---- Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field lastname)

The error disappears if I comment out "lastname: this.newUser.lastName," (9 lines up from the bottom)
I'm truly stumped here. I've looked over all spelling etc and still can seem to find out why this happening.
With that being said is there an easier way to debug angular type script? I'm fairly new to this universe.
import { Injectable } from '#angular/core';
import { AngularFireAuth } from '#angular/fire/auth';
import { AngularFirestore } from '#angular/fire/firestore';
import { Router } from '#angular/router';
import { ThrowStmt } from '#angular/compiler';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class AuthService {
private eventAuthError = new BehaviorSubject<string>("");
eventAuthError$ = this.eventAuthError.asObservable();
newUser: any;
constructor(
private afAuth: AngularFireAuth,
private db: AngularFirestore,
private router: Router) { }
getUserState() {
return this.afAuth.authState;
}
login( email: string, password: string) {
this.afAuth.auth.signInWithEmailAndPassword(email, password)
.catch(error => {
this.eventAuthError.next(error);
})
.then(userCredential => {
if(userCredential) {
this.router.navigate(['/home']);
}
})
}
createUser(user) {
console.log(user);
this.afAuth.auth.createUserWithEmailAndPassword( user.email, user.password)
.then( userCredential => {
this.newUser = user;
console.log(userCredential);
userCredential.user.updateProfile( {
displayName: user.firstName + ' ' + user.lastName
});
this.insertUserData(userCredential)
.then(() => {
this.router.navigate(['/home']);
});
})
.catch( error => {
this.eventAuthError.next(error);
});
}
insertUserData(userCredential: firebase.auth.UserCredential) {
return this.db.doc(`Users/${userCredential.user.uid}`).set({
email: this.newUser.email,
firstname: this.newUser.firstName,
lastname: this.newUser.lastName,
role: 'network user'
})
}
logout() {
return this.afAuth.auth.signOut();
}
}
That error message is telling you that's you're passing the JavaScript value undefined as a property called "lastname" in a document. Firestore doesn't accept undefined values. You will have to assign lastname some other value. If you intend for there always to be a lastname value, you should be checking it for validity before trying to write it into a document field, or leave it out of the document altogether.

How to refresh token in Nestjs

import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { JwtPayload } from './model/jwt-payload.model';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: JwtPayload) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return true;
}
}
Token is extracted from the request by PassportStrategy. I don't know how to catch the error when the token expires or gets invalid. My purpose is if there is an error because the token expired, I need to refresh the token. Otherwise do something else.
Refresh token implementation could be handled in canActivate method in custom auth guard.
If the access token is expired, the refresh token will be used to obtain a new access token. In that process, refresh token is updated too.
If both tokens aren't valid, cookies will be cleared.
#Injectable()
export class CustomAuthGuard extends AuthGuard('jwt') {
private logger = new Logger(CustomAuthGuard.name);
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
try {
const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request);
if (!accessToken)
throw new UnauthorizedException('Access token is not set');
const isValidAccessToken = this.authService.validateToken(accessToken);
if (isValidAccessToken) return this.activate(context);
const refreshToken = request.cookies[REFRESH_TOKEN_COOKIE_NAME];
if (!refreshToken)
throw new UnauthorizedException('Refresh token is not set');
const isValidRefreshToken = this.authService.validateToken(refreshToken);
if (!isValidRefreshToken)
throw new UnauthorizedException('Refresh token is not valid');
const user = await this.userService.getByRefreshToken(refreshToken);
const {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
} = this.authService.createTokens(user.id);
await this.userService.updateRefreshToken(user.id, newRefreshToken);
request.cookies[ACCESS_TOKEN_COOKIE_NAME] = newAccessToken;
request.cookies[REFRESH_TOKEN_COOKIE_NAME] = newRefreshToken;
response.cookie(ACCESS_TOKEN_COOKIE_NAME, newAccessToken, COOKIE_OPTIONS);
response.cookie(
REFRESH_TOKEN_COOKIE_NAME,
newRefreshToken,
COOKIE_OPTIONS,
);
return this.activate(context);
} catch (err) {
this.logger.error(err.message);
response.clearCookie(ACCESS_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
response.clearCookie(REFRESH_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
return false;
}
}
async activate(context: ExecutionContext): Promise<boolean> {
return super.canActivate(context) as Promise<boolean>;
}
handleRequest(err, user) {
if (err || !user) {
throw new UnauthorizedException();
}
return user;
}
}
Attaching user to the request is done in validate method in JwtStrategy class, it will be called if the access token is valid
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
readonly configService: ConfigService,
private readonly userService: UserService,
) {
super({
jwtFromRequest: cookieExtractor,
ignoreExpiration: false,
secretOrKey: configService.get('jwt.secret'),
});
}
async validate({ id }): Promise<User> {
const user = await this.userService.get(id);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Example for custom cookie extractor
export const cookieExtractor = (request: Request): string | null => {
let token = null;
if (request && request.signedCookies) {
token = request.signedCookies[ACCESS_TOKEN_COOKIE_NAME];
}
return token;
};
Instead of using the built-in AuthGuard you can create your own one and overwrite the request handler:
#Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info: Error) {
if (info instanceof TokenExpiredError) {
// do stuff when token is expired
console.log('token expired');
}
return user;
}
}
Depending on what you want to do, you can also overwrite the canActivate method where you have access to the request object. Have a look at the AuthGuard sourcecode.

incorrect imports in angular 6/rxjs of/map etc

I am getting the following errors, trying to update a solution to angular 6.
ERROR in ../app/AuthService.ts
TS2339: Property 'of' does not exist on type 'typeof Observable'.
ERROR in ../app/AuthService.ts
TS2339: Property 'map' does not exist on type 'Observable<any>'.
ERROR in ../app/AuthService.ts
TS2339: Property 'map' does not exist on type 'Observable<boolean>'.
ERROR in TS2339: Property 'map' does not exist on type 'Observable<any>'.
I am using the latest version of rxjs, and the imports (at least on paper) seem correct?
"rxjs": "^6.2.0",
My file is as follows:
import { Injectable } from "#angular/core";
import { NGXLogger } from "ngx-logger";
import { CanActivate, Router } from "#angular/router";
import { HttpClient, HttpHeaders } from "#angular/common/http";
import { Observable } from "rxjs";
import { StorageService } from "./lib/storage/StorageService";
import { map, catchError } from "rxjs/operators";
import { of } from "rxjs";
import * as _ from "lodash";
/**
* class that represents the access token
* returned from /connect/token (IdentityServer 4)
*/
export class Token {
// ReSharper disable once InconsistentNaming
access_token: string;
// the user email.
email: string;
}
#Injectable()
export class AuthService {
/**
* the access token.
*/
token: Token;
constructor(
private readonly http: HttpClient,
private readonly logger: NGXLogger,
private readonly storage: StorageService
) {
}
/**
* return true if the user has a valid token.
*/
isLoggedIn(): Observable<boolean> {
return this
.loadToken()
.map(_ => this.token && this.token.access_token.length > 0)
.catch(e => {
this.logger.debug(e);
this.token = null;
return Observable.of(false);
});
}
logout() {
this.logger.info("logging out");
this.storage.clear("token");
}
/**
* login, using the supplied email and password.
* #param email the email address.
* #param password the password.
*/
login(email: string, password: string): Promise<Token> {
this.logger.info(`user ${email} attempting login`);
const login = {
username: email,
password: password,
grant_type: "password",
scope: "api",
client_id: "api",
client_secret: "secret"
};
const headers: HttpHeaders = new HttpHeaders().set("Content-Type", "application/x-www-form-urlencoded");
const post = this.http.post<Token>(
"connect/token",
this.encodeObjectToParams(login),
{ headers: headers });
return post
.toPromise()
.then((_: Token) => this.token = this.saveToken(_));
}
register(email: string, password: string, confirmPassword: string): Observable<any> {
this.logger.info(`user ${email || "<null>"} registration request`);
const uri = "api/register";
const registration = {
email,
password,
confirmPassword
};
return this.http
.post<any>(
uri,
registration)
.map(_ => _)
.catch(e => {
this.logger.debug(`exception :: ${uri} :: `, e);
return Observable.throw(e.error);
});
}
/**
* encode the supplied object to a set of form variables.
* #param instance the object to encode.
*/
private encodeObjectToParams(instance: any): string {
return Object.keys(instance)
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(instance[key]))
.join("&");
}
/**
* save the supplied token
*/
private saveToken = (token: Token): Token => this.storage.save("token", token);
/**
* attempt to load the token from local storage,
* and test that it is valid.
*/
private loadToken() {
this.logger.debug("loading 'token'");
const token = this.storage.load<Token>("token");
return this.testToken(token)
.map(_ => this.token = token);
}
/**
* test that the supplied login token works.
* #param token the token to test.
*/
isTokenEmailValid = token => this.http
.get<any>("api/user")
.map((response: any) => response !== null && response.isAuthenticated);
testTokenThrottle = _.throttle(token => {
this.logger.info(`testing token :: ${JSON.stringify(token)}`);
return this.isTokenEmailValid(token);
}, 300000);
private testToken(token: Token): Observable<boolean> {
const valid = this.testTokenThrottle(token) || this.isTokenEmailValid(token);
return valid;
}
}
/**
* Authorisation guard, to ensure that a user
* not logged in is redirected to the login page.
*/
#Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly logger: NGXLogger,
private readonly router: Router) { }
canActivate(): Observable<boolean> {
this.logger.debug("authorisation guard testing login status");
return this.authService.isLoggedIn().map(_ => {
this.logger.info(`authorisation guard :: login status ${_}`);
if (_)
return true;
this.router.navigate(["login"]);
return false;
}).catch(_ => {
this.logger.info(`authorisation guard :: login error ${_}`);
this.router.navigate(["login"]);
return Observable.of(false);
});
}
}
From RxJs 6.0 you've to use pipe() operator which takes infinte number of other operators that will apply the Observable .
The error message Property 'map' does not exist on type 'Observable<any>' is meaningful, map is not exists in Observable.
You can't chain Observable.map(...).catch(...).
The new syntax is Observable.pipe( map(...), catchError(...) )
Example:
import { map, catchError } from "rxjs/operators";
import { of } from "rxjs";
isLoggedIn(): Observable<boolean> {
return this
.loadToken()
.pipe(
map(_ => this.token && this.token.access_token.length > 0),
catchError(e => {
this.logger.debug(e);
this.token = null;
return of(false);
})
)
}
One Refernece: https://www.academind.com/learn/javascript/rxjs-6-what-changed/#operators-update-path
// rxjs 5
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map'
OR
import { of } from 'rxjs/observable/of';
import { map } from 'rxjs/operator/map'
AND
// rxjs 6 alpha
import { Observable } from 'rxjs';
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

Categories

Resources