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);
});
}
});
});
}
Related
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
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)
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.
I have a service that makes request to an api and according to its response I should decide that a component must be loaded or not. but because of a time spent to receive the response, component loads regardless of response status, and after some time (about 0.5 secs) response is received and if the component must not be loaded, we navigate to somewhere else. I don't want the component to be loaded before receiving the response.
I'm using canActivate function from AuthGuard in angular 4 as below:
export class AuthGuard implements CanActivate {
access = true;
res: any;
constructor(private router: Router, private routeService: RouteService){}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
setTimeout(() => {
if( !this.exept.includes(this.router.url) ){
this.routeService.FormOperation(this.router.url).subscribe(item=> {
this.res = item;
if (this.res.status == 200) {
if (this.res.data.Access[1] == false) {
this.access = false;
}
if (this.access == true)
{
return true;
}
else {
this.router.navigate(['/dashboard']);
return false;
}
})
}
},0);
if (sessionStorage.getItem('token') && this.access)
{
// logged in so return true
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
I'm using setTimeout so that I can get a correct this.router.url .
Update:
I added resolver as below:
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): void {
this.routeService.FormOperation(this.router.url).toPromise()
.then(response => {
this.form_operation_data = response;
if(!this.form_operation_data['data']['Access'][1]) {
this.router.navigate(['/dashboard']);
}
})
.catch(err => {
console.error(err);
});
}
but still the component loads before response data receives ...
You're so close: your AuthGuard should return true or false, and based on this value, the route will be activated or not. Now you have to add this auth guard to your routing (here is an example for activating child route). And if you want to fetch data before component load, you can use resolvers.
Resolver
/* Imports */
#Injectable()
export class DataResolverService {
constructor(
private _serviceToShareData: ServiceToShareData,
private _serviceToGetData: ServiceToGetData,
) { }
/* An example with Promise */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<void> {
return this._serviceToGetData.anyRequestToApi()
.then(response => this._serviceToShareData.setData(response))
.catch(err => {
console.error(err);
});
}
}
And now you can get fetched data from ServiceToShareData service in your component, which you want to load with this data.
Routing module
/* Other imports */
import {DataResolverService } from './path-to-service/data-resolver-service'
import {AuthGuard} from './path-to-service/auth-guard'
const routes: Routes = [
{
path: 'app',
component: ParentComponent,
children: [
{
path: 'child-route',
component: childRouteComponent,
canActivate: [AuthGuard],
resolve: {
data: DataResolverService
}
}
]
}
];
/* Other stuff like #NgModule and class export*/
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