I've one question. I don't understand why, but my services is undefined. Someone could help me to clarify this ?
I've a component "FormLogin" with the call of this service
<script>
import { authenticationService } from '#/container.js'
import { ref } from '#vue/reactivity'
export default {
emits: ['successfullyLogged'],
setup (props, context) {
const errors = ref([])
const email = ref(null)
const password = ref(null)
const submit = () => {
errors.value = []
authenticationService()
.login(email.value, password.value)
.then(() => {
context.emit('successfullyLogged')
})
.catch(error => {
errors.value = [error.response.data.message]
})
}
return {
email,
password,
errors,
submit
}
}
}
</script>
Then i've the "global injection"
import api from '#/clients/api.js'
import TokenRepository from '#/repositories/TokenRepository.js'
import AuthenticationService from '#/services/AuthenticationService.js'
export function tokenRepository () {
return new TokenRepository()
}
export function authenticationService () {
return new AuthenticationService(api, tokenRepository)
}
And after that, the service itself
import store from "#/store"
export default (client, tokenRepository) => {
const login = (email, password) => {
return client.post('/oauth/token', {
grant_type: 'password',
client_id: process.env.VUE_APP_CLIENT_ID,
client_secret: process.env.VUE_APP_CLIENT_SECRET,
username: email,
password: password
})
.then(response => {
tokenRepository().store(response.data.access_token)
store.dispatch('account/loadUser')
})
}
const logout = () => {
tokenRepository().destroy()
store.commit('account/setUser', {})
}
return {
login,
logout
}
}
But when i run this code, fill my form fields and hit the button "submit", i've this error in console, and i don't undestand why. (And when i try to use the debugger, it appear that authenticationService in FormLogin is undefined.
Thanks in advance for your help,
Christophe
So the answer of the problem was to remove the word "new" in my container.js
Bad answer for me
export function authenticationService () {
return new AuthenticationService(api, tokenRepository)
}
Good answer for me
export function authenticationService () {
return AuthenticationService(api, tokenRepository)
}
Related
So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }
How do i pass some dynamic params in the facebook login callback url?
I have different types of users (differentiated by a 'type' param) signing up using facebook login. I have created a facebook auth strategy using passport-facebook which works fine.
However after authentication, when callback url is called, i need to know which type of user requested the signup.
I'm guessing i can pass a param when defining the callback url
something like this
http://localhost:3000/auth/facebook/callback/type1
http://localhost:3000/auth/facebook/callback/type2
How do I pass a dynamic value into the FacebookStrategy??
or whats the possible workaround to achieve this?
// PassportStrategy.ts
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
clientID: 'MYID',
clientSecret: 'MYSCRET',
callbackURL: "http://localhost:3000/auth/facebook/callback",
profileFields: ['id', 'displayName', 'emails', 'photos']
});
}
async validate(accessToken: any, refreshToken: any, profile: any) {
return {
name: profile.displayName,
email: profile.emails[0].value,
provider: "facebook",
providerId: profile.id,
photo: profile.photos[0].value
}
}
}
// auth controller
#Controller('auth')
export class AuthController {
constructor(
#Inject(forwardRef(() => AuthService)) private readonly authService: AuthService,
) { }
#Get('/facebook')
#UseGuards(AuthGuard('facebook'))
async facebookAuth(#Request() req) {
return
}
#UseGuards(AuthGuard('facebook'))
#Get('/facebook/callback')
async facebookCallback(#Request() req) {
return this.authService.login(req.user);
}
}
Basically i want to be able to call "/auth/facebook/:type" and pass the type value in the callback url defined in the Strategy
and callback endpoint to be something like "/auth/facebook/callback/:type"
so when i call the authservice.login function i can pass that 'type' and decide which type of user to be created if its the first time signup
Guide me if my approach is wrong. Thanks
I have been dealing recently with a similar issue here is my approach. Probably is not the best but works for now.
import { Inject, Injectable, Logger } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import passport = require('passport');
import { Strategy } from 'passport-facebook';
#Injectable()
export class FacebookStrategy extends PassportStrategy(Strategy, 'facebook') {
private readonly logger = new Logger(FacebookStrategy.name);
constructor(
#Inject('FACEBOOK_STRATEGY_CONFIG')
private readonly facebookStrategyConfig,
) {
super(
facebookStrategyConfig,
async (
request: any,
accessToken: string,
refreshToken: string,
profile: any,
done,
) => {
this.logger.log(profile);
// take the state from the request query params
const { state } = request.query;
this.logger.log(state);
// register user
// return callback
return done(null, profile);
},
);
passport.use(this);
}
}
import { Controller, Get, HttpStatus, Inject, Param, Query, Req } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { Redirect } from '#nestjsplus/redirect';
#Controller('auth')
export class AuthController {
#Inject('ConfigService')
private readonly configService: ConfigService;
#Get(':provider/callback')
#Redirect()
async socialCallback(#Req() req, #Param('provider') provider: string, #Query('state') state: string) {
// here you can use the provider and the state
return {
statusCode: HttpStatus.FOUND,
url: `${this.configService.get('FRONTEND_HOST')}/dashboard`,
};
}
}
import { MiddlewareConsumer, Module, NestModule } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { AuthController } from './auth.controller';
import { FacebookStrategy } from './facebook.strategy';
import passport = require('passport');
const facebookStrategyConfigFactory = {
provide: 'FACEBOOK_STRATEGY_CONFIG',
useFactory: (configService: ConfigService) => {
return {
clientID: `${configService.get('FACEBOOK_CLIENT_ID')}`,
clientSecret: `${configService.get('FACEBOOK_CLIENT_SECRET')}`,
callbackURL: `${configService.get('FACEBOOK_OAUTH_REDIRECT_URI')}/callback`,
profileFields: ['id', 'displayName', 'link', 'photos', 'emails', 'name'],
passReqToCallback: true,
};
},
inject: [ConfigService],
};
#Module({
controllers: [AuthController],
providers: [facebookStrategyConfigFactory, FacebookStrategy],
})
export class AuthModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
}
}
Now let me explain a little bit :D. The trick is in the middleware configuration.
const facebookLoginOptions = {
session: false,
scope: ['email'],
state: null,
};
consumer
.apply((req: any, res: any, next: () => void) => {
const {
query: { state },
} = req;
facebookLoginOptions.state = state;
next();
}, passport.authenticate('facebook', facebookLoginOptions))
.forRoutes('auth/facebook/*');
So, oAuth has this feature that you can pass a state param through the login flow.
By extracting the passport option in a variable we can change the state param dynamically by applying another middleware before the passport one.
In this way, you can call now http://localhost:3000/auth/facebook/login?state=anything-you-want
and this state query param will be passed through the strategy and also in the callback call.
I have also created a git repo with the example: https://github.com/lupu60/passport-dynamic-state
Another approach: the need was to dynamically set server url. It gets it using Context/Request.
// Custom Guard:
export const DynamicAuthGuard = (type?: string): Type<IAuthGuard> => {
const endpoint = `auth/${type}/redirect`
return class extends AuthGuard(type) {
getAuthenticateOptions(context: ExecutionContext) {
const httpContext: HttpArgumentsHost = context.switchToHttp()
const req: Request = httpContext.getRequest<Request>()
const serverURL = `${req.protocol}://${req.get('host')}`
const args = 'foo=bar'
const callbackURL = `${serverURL}/${endpoint}?${args}`
return {callbackURL}
}
}
}
// In controller 'auth':
#UseGuards(DynamicAuthGuard('facebook')) // or any passport strategy
#Get('facebook/redirect')
async facebookRedirect(#Req() req: Request, #Res() res: Response) {
// ...
}
I am trying to use #waves/waves-crypto I have import * as wavesCrypto from '#waves/waves-crypto' in my .ts file but I am still getting error within the npm module itself. I am trying to create a waves wallet using nativescript and right now I am trying to create the address and seed and public and private key for the user. this is login.ts where im calling the #waves/waves-crypto
import { Component, ElementRef, ViewChild } from "#angular/core";
import { Router } from "#angular/router";
import { alert, prompt } from "tns-core-modules/ui/dialogs";
import { Page } from "tns-core-modules/ui/page";
import { Routes } from "#angular/router";
//import { publicKey, verifySignature, signBytes, address, keyPair, privateKey } from "../#waves/waves-crypto";
import * as wavesCrypto from '../#waves/waves-crypto';
import { User } from "../shared/user.model";
import { UserService } from "../shared/user.service";
#Component({
selector: "app-login",
moduleId: module.id,
templateUrl: "./login.component.html",
styleUrls: ['./login.component.css']
})
export class LoginComponent {
isLoggingIn = true;
user: User;
#ViewChild("password") password: ElementRef;
#ViewChild("confirmPassword") confirmPassword: ElementRef;
#ViewChild("waves") waves: ElementRef;
constructor(private page: Page, private userService: UserService, private router: Router) {
this.page.actionBarHidden = true;
this.user = new User();
// this.user.email = "foo2#foo.com";
// this.user.password = "foo";
const seed = 'magicseed';
const pubKey = wavesCrypto.publicKey(seed);
const bytes = Uint8Array.from([1, 2, 3, 4]);
const sig = wavesCrypto.signBytes(bytes, seed);
const isValid = wavesCrypto.verifySignature(pubKey, bytes, sig)
}
wallet() {
let walletAddress = wavesCrypto.address('seed', 'T');
let keyPair = wavesCrypto.keyPair('seed');
//publicKey('seed');
//privateKey('seed');
wavesCrypto.privateKey('seed');
alert(walletAddress);
console.log(walletAddress);
console.log(keyPair);
}
toggleForm() {
this.isLoggingIn = !this.isLoggingIn;
}
submit() {
if (!this.user.email || !this.user.password) {
this.alert("Please provide both an email address and password.");
return;
}
if (this.isLoggingIn) {
this.login();
} else {
this.register();
}
}
login() {
this.userService.login(this.user)
.then(() => {
this.router.navigate(["/home"]);
})
.catch(() => {
this.alert("Unfortunately we could not find your account.");
});
}
register() {
if (this.user.password != this.user.confirmPassword) {
this.alert("Your passwords do not match.");
return;
}
this.userService.register(this.user)
.then(() => {
this.alert("Your account was successfully created.");
this.isLoggingIn = true;
})
.catch(() => {
this.alert("Unfortunately we were unable to create your account.");
});
}
forgotPassword() {
prompt({
title: "Forgot Password",
message: "Enter the email address you used to register for APP NAME to reset your password.",
inputType: "email",
defaultText: "",
okButtonText: "Ok",
cancelButtonText: "Cancel"
}).then((data) => {
if (data.result) {
this.userService.resetPassword(data.text.trim())
.then(() => {
this.alert("Your password was successfully reset. Please check your email for instructions on choosing a new password.");
}).catch(() => {
this.alert("Unfortunately, an error occurred resetting your password.");
});
}
});
}
focusPassword() {
this.password.nativeElement.focus();
}
focusConfirmPassword() {
if (!this.isLoggingIn) {
this.confirmPassword.nativeElement.focus();
}
}
alert(message: string) {
return alert({
title: "APP NAME",
okButtonText: "OK",
message: message
});
}
}
I have the same problem and I opened the next issue on the Github repo (you can go and click like or comment), link here
In the issue I explain a workaround that is working for me to validate a signature, you can use the same snippet.
First import manually the submodules needed:
import { default as axlsign } from '#waves/signature-generator/libs/axlsign';
import { default as convert } from '#waves/signature-generator/dist/utils/convert';
import { concatUint8Arrays } from '#waves/signature-generator/dist/utils/concat';
import { default as base58 } from '#waves/signature-generator/dist/libs/base58';
Then you can use the next code to validate the signature and publickey:
let prefix = "WavesWalletAuthentication";
let host = new URL(yourServerUrl).hostname;
let user = wavesAddressString;
let payload = theStringThatWasSigned;
let data = [prefix, host, payload]
.map(d => convert.stringToByteArrayWithSize(d))
.map(stringWithSize => Uint8Array.from(stringWithSize));
let dataBytes = concatUint8Arrays(...data);
let publicKeyBytes = base58.decode(publicKeyOnBase58Format);
let signatureBytes = base58.decode(signatureOnBase58Format);
let validSignature = axlsign.verify(publicKeyBytes, dataBytes, signatureBytes);
console.log("(login) validSignature?", validSignature);
First, I must mention that I'm a beginner in Angular and I'm kind of stucked with my sample code.
I created some simple login app which prompts for username and password, calls login REST service (written in Java) that returns some token at login success or throws an exception at login failure.
Here's some of my code.
Login component:
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { AuthenticationService } from '../_services/index';
#Component({
moduleId: module.id,
templateUrl: 'login.component.html'
})
export class LoginComponent implements OnInit {
model: any = {};
error = '';
constructor(
private router: Router,
private authenticationService: AuthenticationService) { }
ngOnInit() {
// reset login status
this.authenticationService.logout();
}
login() {
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result === true) {
this.router.navigate(['/']);
} else {
this.error = 'Login failed!';
}
},
err => {
this.error = 'Login failed!';
});
}
}
Authentication service:
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions, Response } from '#angular/http';
import { Observable } from 'rxjs';
import { CookieService } from 'angular2-cookie/core';
import { CookieOptionsArgs } from 'angular2-cookie/services/cookie-options-args.model';
import 'rxjs/add/operator/map';
#Injectable()
export class AuthenticationService {
public token: string;
constructor(private http: Http, private cookieService: CookieService) {
// set token if saved in cookie
this.token = cookieService.get('token');
}
login(username, password): Observable<boolean> {
return this.http.post('http://localhost:9081/MyApp/login?username=' + username + '&password=' + password, new RequestOptions({}))
.map((response: Response) => {
// login successful if there's a token in the response
let token = response.text();
if (token !== '-1') {
// set token
this.token = token;
// store token in cookie to keep user logged
let opts: CookieOptionsArgs = {
path: '/'
};
this.cookieService.put('token', token, opts);
// return true to indicate successful login
return true;
} else {
// return false to indicate failed login
return false;
}
});
}
logout(): void {
// clear token, remove cookie to log user out
this.token= null;
this.cookieService.remove('token');
}
}
Everything works as expected. When login is successful, token is returned and I'm redirected to a "home" page. Otherwise, a "Login falied" message appears on a login page and no redirection occurs. What bothers me is that I don't exactly know why login fails: is it because username doesn't exist or is it maybe because password is wrong. What is the proper way to handle exceptions thrown by REST service? I assume that authentication service is the right place but I don't exactly know how to do it. I tried to extract some info from request object but request mapping doesn't happen if exception is thrown.
Thanks for help!
It seems you're looking for catching the exception occuring on error login in AuthenticationService . If it's the case add .catch section after .map, like in this subject :
best practives catching error Angualr 2
.catch((error: any) => { //catch Errors here using catch block
if (error.status === 500) {
// Display your message error here
}
else if (error.status === 400) {
// Display your message error here
}
});
i have implemented my code this way :
login(email: string, password: string): Observable<boolean> {
return new Observable(observer => {
var data = { email: email, password: password };
this.http.post(this.server_url + '/auth/authenticate', data).subscribe(x => {
var result = {
email: x.json().email,
token: x.json().token,
roles: x.json().roles.map(x => x.name)
}
localStorage.setItem(this._userKey, JSON.stringify(result));
observer.next(true);
observer.complete();
}, er => {
if (er.status == 401) {
observer.next(false);
observer.complete();
} else {
console.log(er);
observer.error(er);
observer.complete();
}
});
});
}
so it handle three possibilities :
if cridential is OK it returns true
if credential is wrong return false (remember your server must
return 401 status !)
otherwise there is problem in server and throw error
and in handler i got :
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(result => {
if (result == true) {
this.router.navigate(['/home']);
} else {
this.error = 'Username or password is incorrect';
this.loading = false;
}
}, err => {
this.error = 'Unexpected error occured.. please contact the administrator..';
this.loading = false;
});
}
I'm building an observable data service based on the following article: https://coryrylan.com/blog/angular-2-observable-data-services
In the article he used an array as an example, here I will use the user object since I'm developing the user service.
Here's what I got:
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Events, SqlStorage, Storage } from 'ionic-angular';
import { Subject } from 'rxjs/Subject';
export interface DataStore {
user: Object
}
#Injectable()
export class UserService {
private baseUrl: string;
private storage: Storage;
private _user$: Subject<Object>;
private dataStore: DataStore;
constructor(
private http: Http
) {
this.baseUrl = 'http://localhost:3000';
this.storage = new Storage(SqlStorage);
this._user$ = <Subject<Object>>new Subject();
this.dataStore = {
user: { name: '' }
};
}
set user$(user: Object) {
this.storage.set('user', JSON.stringify(user));
this.dataStore.user = user;
this._user$.next(this.dataStore.user);
}
get user$() {
return this._user$.asObservable();
}
loadUser() {
return this.storage.get('user').then(
((user: string): Object => {
this.dataStore.user = JSON.parse(user);
this._user$.next(this.dataStore.user);
return this.dataStore.user;
})
);
}
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', { access_token: accessToken })
.retry(2)
.map((res: Response): any => res.json());
}
logout(): void {
this.storage.remove('user');
}
}
To authenticate I call the login() function and set the user data if everything ok.
this.userService.login(this.data.accessToken)
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
I feel it is better set the user data inside the service. I could do the following:
login(accessToken: string) {
return this.http
.post('http://localhost:3000/login', {
access_token: accessToken
})
.retry(2)
.map((res: Response): any => res.json())
.subscribe(
(user: Object) => {
this.userService.user$ = user;
this.nav.setRoot(EventListComponent);
},
(error: Object) => console.log(error)
);
}
But I won't be able to subscribe to the login() function in the component since it's already subscribed. How could I redirect the user if everything ok or show an alert if anything goes wrong in the component but setting the user inside the service?
In the main component I load the user data and set the rootPage:
this.userService.loadUser().then(
(user: Object) => this.rootPage = EventListComponent,
(error: Object) => this.rootPage = LoginComponent
);
I thought that calling the loadUser() function at this time I would not have to call it again, but I have to call it in all components that I need the user data:
this.user = this.userService.user$;
this.userService.loadUser();
I don't think the service is the way it should, what could I improve? Is there any better way to achieve what I want? Any example or idea?