CORS blocked error with loadDiscoveryDocumentAndTryLogin() - azure-active-directory

I am working on Angular 9 Authentication from Azure B2C. In angular, I am using Auth angular-oauth2-oidc library.
I am getting CORS block error if I have loadDiscoveryDocumentAndTryLogin() code in config method not really sure what I am missing. If I disable loadDiscorveryDocumentAndTryLoging() code then I manage to redirect to Azure B2C login page
import { OnInit, Component, Injector } from '#angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { JwksValidationHandler } from 'angular-oauth2-oidc-jwks';
import { authConfig } from '../../config/auth.config';
#Component({
selector: 'app-auth-landing-page',
templateUrl: 'auth-landing.component.html',
styleUrls: ['auth-landing.component.scss']
})
export class AuthComponent implements OnInit{
_accessToken: string;
_idToken: string;
constructor(private injector: Injector) { }
private get oauthService() {
return this.injector.get(OAuthService)
}
private async ConfigureAuth(): Promise<void>{
this.oauthService.configure(authConfig);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.loadDiscoveryDocumentAndTryLogin();
this.oauthService.setStorage(sessionStorage);
}
async ngOnInit(){
await this.ConfigureAuth();
}
async login(){
this.oauthService.tryLogin({});
if(!this.oauthService.getAccessToken()){
await this.oauthService.initImplicitFlow();
}
this._accessToken = this.oauthService.getAccessToken();
this._idToken = this.oauthService.getIdToken();
let validToken = this.oauthService.hasValidAccessToken();
console.log(this._accessToken);
}
logout(){
this.oauthService.logOut();
}
token(){
let claims: any = this.oauthService.getIdentityClaims();
return claims ? claims : null;
}
readToken()
{
console.log("ID Token ", this._accessToken );
console.log("Access Token", this._idToken);
let claims: any = this.oauthService.getIdentityClaims();
console.log(claims);
}
}

Metadata URL is malformed. Use the following in your OAuthService configuration:
loginUrl: "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/oauth2/v2.0/authorize",
issuer: "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{policy}/v2.0/",
Also, not issue related but worth mentioning:
You're not awaiting anything in this function:
private async ConfigureAuth(): Promise<void>{
And/or more importantly, avoid async ngOnInit unless you're ok with the implications.

Related

I cannot able to return the json on the react from linked in auth using Nestjs

I want to return the linkedin auth data to the react app after getting data on the callback URL. But i am unable to send that data. I have tried to redirect to another route by passing linked in data as object but it is not working so please help me to get that data in my react app from nest js. Also i cannot able to save my data in firebase auth table of this linkedin credential. I am sharing my app.module.js and linedin.controller.js file so that you can check and tell me what to do next.
Linkedin.controller.js
import { Controller, Get, Redirect, Req, Res } from "#nestjs/common";
import { ApiTags } from "#nestjs/swagger";
import {
UseLinkedinAuth,
LinkedinAuthResult,
} from '#nestjs-hybrid-auth/linkedin';
import { Response } from "express";
#ApiTags('linkedInUser')
#Controller('user')
export class LinkedController {
#UseLinkedinAuth()
#Get('auth/linkedin')
loginWithLinkedin() {
return 'Login with Linkedin';
}
#UseLinkedinAuth()
#Get('auth/linkedin/callback')
#Redirect()
linkedinCallback(#Req() req:any,#Res() res: Response): Partial<LinkedinAuthResult> {
const result: LinkedinAuthResult = req.hybridAuthResult;
return {
accessToken: result.accessToken,
refreshToken: result.refreshToken,
profile: result.profile,
}
}
}
app.module.js
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { AuthController } from './controllers/auth.controller';
import { ProductController } from './controllers/product.controller';
import { UserController } from './controllers/user.controller';
import { FirebaseAuthService } from './services/firebase.service';
import { AuthMiddleware } from './middleware/auth.middleware';
import { LinkedController } from './controllers/linkedinController';
import { LinkedinAuthModule } from '#nestjs-hybrid-auth/linkedin';
import * as config from '../config/linkedinConfig.json'
#Module({
imports: [MongooseModule.forRoot(
'mongodb+srv://ayush:<password>#cluster0.mmafg9g.mongodb.net/nestdemo'
),LinkedinAuthModule.forRoot({
clientID: config.linkedinAuth.clientID,
clientSecret: config.linkedinAuth.clientSecret,
callbackURL: config.linkedinAuth.callbackURL,
}),],
controllers: [AuthController,UserController,ProductController,LinkedController],
providers: [FirebaseAuthService],
exports: [FirebaseAuthService]
})
export class AppModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes({path: '/api/v1/product', method:
RequestMethod.ALL});
}
}

How to implement Passport.js Azure AD Bearer Strategy ( OpenID ) in NestJS

Following the documentation for nestjs and passport I have the following implementation.
I started with the default nest cli setup, nest new auth-test I then added an auth folder under the src folder where the aad-auth.gaurd, aad.strategy and auth.module below sit.
I then added the new guard to the default route in the app.controller.ts
I confirmed the Azure App Registration setup by using it successfully as a C# Web API, so the Azure side is setup correctly.
I don't need to issue a JWT as that is issues in the front end by Azure AD, that bearer token is passed to the API in the header. There are no helpful errors, simply a 500 Internal Error. I notice a lot of Github requests for documentation on implementing Azure AD with nest, along with any OAuth flow provider (facebook, google) but as yet that request is still open.
Not sure what is implemented wrong, any guidance or suggestions would be appreciated on fixing the below code.
Documentation:
NestJS : https://docs.nestjs.com/techniques/authentication
Passport : http://www.passportjs.org/packages/passport-azure-ad/
//auth/aad.strategy.ts
import { BearerStrategy } from 'passport-azure-ad';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, ValidationPipe } from '#nestjs/common';
#Injectable()
export class AADStrategy extends PassportStrategy(BearerStrategy) {
constructor () {
super({
identityMetadata: "https://login.microsoftonline.com/<tenant>.onmicrosoft.com/v2.0/.well-known/openid-configuration",
clientID: "<clientid>",
issuer: null,
audience: null,
loggingLevel: "info",
passReqToCallback: false
})
}
async validate(payload: any){
console.log(payload);
return(payload);
}
}
//auth/aad-auth.gaurd
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class AADAuthGaurd extends AuthGuard('aad') {}
//auth/auth.module.ts
import { Module } from '#nestjs/common';
import { AADStrategy } from './aad.strategy'
#Module({
imports: [
AADStrategy,
],
providers: [
AADStrategy,
]
})
export class AuthModule {}
//app.controller.ts
import { Controller, Get, UseGuards } from '#nestjs/common';
import { AppService } from './app.service';
import { AADAuthGaurd } from './auth/aad-auth.gaurd';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#UseGuards(AADAuthGaurd)
#Get()
getHello(): string {
return this.appService.getHello();
}
}
I resolved the issue, here is the working code condensed to one file.
//app.controller.ts
import { Controller, Get, Injectable, UseGuards } from '#nestjs/common';
import { AppService } from './app.service';
import { AuthGuard, PassportStrategy } from "#nestjs/passport";
import { BearerStrategy } from 'passport-azure-ad'
#Injectable()
export class AzureADStrategy extends PassportStrategy(BearerStrategy, 'oauth-bearer')
{
constructor()
{
super({
identityMetadata: `https://login.microsoftonline.com/<tenant>.onmicrosoft.com/.well-known/openid-configuration`,
clientID: 'client id from azure app',
})
}
async validate(response: any)
{
const { unique_name }: {unique_name: string} = response;
if (unique_name) return unique_name;
else return null;
}
}
#Controller()
export class AppController {
constructor() {}
#Get('unprotected')
unprotected() : string {
return 'Unprotected';
}
#UseGuards(AuthGuard('oauth-bearer'))
#Get('protected')
protected() : string {
return 'Protected';
}
}

Trying to execute component function after auth.login() with Facebook Auth0 Angular 2

I'm trying to execute a component function after Auth0 logs someone in via Facebook authentication. I'm trying to have that component function send a POST request with the Facebook profile object (along with another property I add in) to the ExpressJS server, which then saves it to MongoDB. The problem is, I haven't been able to get it to work. I'm wondering if I could modify the auth.login() function to instead return a promise, and take in a the extra parameter I need, and then subscribe to that from my component, and on success send the object. I've also tried to just put the POST request itself in the Auth Service constructor, but that hasn't worked. Here is my code so far:
home.html
...
<button class="btn btn-primary pull-left" type="submit" (click)="someFunction('extraProperty')">Submit</button>
...
home.component.ts
import { Component } from '#angular/core';
import { HomeService } from './home.service';
import { Auth } from './auth.service';
#Component({
moduleId: module.id,
selector: 'my-home',
templateUrl: 'home.html',
})
export class HomeComponent {
constructor(private homeService: HomeService, private auth: Auth) { }
someFunction(extraProperty: string): void {
// Saving extraProperty to local storage, then authenticating
localStorage.setItem('extraProperty', extraProperty);
this.auth.login();
}
}
home.service.ts
import { Injectable } from '#angular/core';
import {Observable} from 'rxjs/Rx';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Auth } from './auth.service';
#Injectable()
export class HomeService {
constructor (private http: Http) {}
private serverUrl = 'http://localhost:8000/';
// Trying to call this function after Auth Service login
postVote(body: Object): Observable<Boolean[]> {
return this.http.post((this.serverUrl + 'postVote'), body)
.map((res:Response) => {
console.log("Res is: ", res);
})
.catch((error:any) => Observable.throw(error.json().error || 'Server error'));
}
}
auth.service.ts
import { Injectable } from '#angular/core';
import { tokenNotExpired } from 'angular2-jwt';
import { HomeService } from './home.service';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import {Observable} from 'rxjs/Rx';
// Avoid name not found warnings
declare var Auth0Lock: any;
#Injectable()
export class Auth {
// Configure Auth0
lock = new Auth0Lock('CLIENT_ID', 'DOMAIN');
userProfile: any;
constructor(private homeService: HomeService, private http: Http) {
// Set userProfile attribute of already saved profile
this.userProfile = JSON.parse(localStorage.getItem('profile'));
// Add callback for lock `authenticated` event
this.lock.on("authenticated", (authResult) => {
localStorage.setItem('id_token', authResult.idToken);
// Fetch profile information
this.lock.getProfile(authResult.idToken, (error, profile) => {
if (error) {
// Handle error
alert(error);
return;
}
profile.extraProperty = localStorage.getItem('extraProperty');
localStorage.setItem('profile', JSON.stringify(profile));
this.userProfile = profile;
console.log(profile);
// Tried calling the service here, but I don't think it would work anyways in the constructor.
this.homeService.postVote(this.userProfile);
});
});
}
public login() {
// Call the show method to display the widget.
this.lock.show();
};
...
}
If I could also go about this in any other way that would be easier that you know of, that would be helpful as well. Essentially I'm trying to get the user's profile, save it to a DB in order to match them up with one person that holds the corresponding extraProperty, redirect to a new module and render the profile pic and name of the person with their match.
Thanks in advance!
Edit: Here is the function postVote on the server side. Hope that helps
// Adding vote object to MongoDB
router.post('/postVote', function(req, res, next) {
console.log("Req.body is: ", req.body);
Vote.save(function(err) {
if(err) {
throw err;
// return err;
}
console.log("Vote saved");
});
res.sendStatus(200);
});

Angular2 shared observable

Doing some experimentation with Angular2 and curious about how to solve a situation where a service exposes a shared observable. Where one component is responsible for getting the data and another is responsible for displaying the data. Here is some code:
The common HttpService responsible for getting the data
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
#Injectable()
export class Service {
subject = new Subject<string[]>;
observable$ = this.subject.asObservable();
constructor(private http: Http) {}
observable: Observable<string[]>;
get(link: string): Observable<string[]> {
this.observable$ = this.http.get('myapi.com')
.map((res: Response) => this.subject.next(res.json())
.catch(this.handleError);
return this.observable$;
}
/**
* Handle HTTP error
*/
private handleError (error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}
The GetComponent responsible for getting the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Service } from '../shared/index';
#Component({
moduleId: module.id,
selector: 'get',
templateUrl: 'get.component.html',
providers: [Service],
})
export class GetComponent {
constructor(public service: Service) {}
submit() {
this.service.get(this.url).subscribe();
}
}
The DisplayComponent responsible for displaying the data
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription';
#Component({
moduleId: module.id,
selector: 'display',
templateUrl: 'display.component.html'
})
export class DisplayComponent {
subscription: Subscription;
constructor(public service: Service) {}
ngOnInit() {
// the observable is undefined :(
this.subscription = this.service.observable$.subscribe(data => {
console.log(data);
},
error => {
// this never gets reached :(
})
}
ngOnDestroy() {
// prevent memory leak when component is destroyed
this.subscription.unsubscribe();
}
}
This design works ok except the error handling does not work for the DisplayComponent. Also the map function in the HttpService doesn't seem quite right this.subject.next(res.json()
Also it doesn't seem quite right that the GetComponent has to "subscribe" like this this.service.get(this.url).subscribe();
What is the proper way to design this sort of thing? How can I get the DisplayComponent to observe errors thrown by the HttpComponent
Your code has several problems where Service.$observable is modified several times:
observable$ = this.subject.asObservable();
this.observable$ = this.http.get('myapi.com') .... // remove this line
The right way to do this:
Get Component calls Service to get data. After data is loaded, Get Component emits a Data Ready event with the data. Display Component, and other components that use this data, listen to DataReady event and update data when the event is emitted.
Code to explain my answer:
#Injectable()
export class GetService {
/// subject
subject = new Subject<string[]>()
/// the observable
observable = this.subject.asObservable()
constructor(private $http: Http) {
}
/// get data
getData() {
// not override observable here
this.$http.get("api.com")
.map(response => response.json()) // map the data
.subscribe((data: string[]) => this.subject.next(data), // emit data event
error => this.subject.error(error)) // emit error event
}
}

We want to develop an authentication guard in Angular 2. Can we navigate/redirect to any login url?

Can we navigate/redirect to any login url (different host and application) or must we navigate/redirect only to url's within the routes of our application?
The example from the angular site suggests only application routes are permitted:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.authService.isLoggedIn) { return true; }
// Store the attempted URL for redirecting
this.authService.redirectUrl = state.url;
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
Have you tried navigateByUrl()?
See https://angular.io/docs/ts/latest/api/router/index/Router-class.html for usage.
But:
Why you need an "external" URL not part of your application?
I assume best practive would be an external authService instead of an seperate "Login-Page-Application". or such.
e.g.
import { Injectable } from '#angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from '#angular/router';
import {Observable} from "rxjs";
import {AuthService} from "../services/auth.service";
#Injectable()
export class LoginGuard implements CanActivate {
constructor(protected router: Router, protected authService: AuthService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
console.log('AuthGuard#canActivate called');
if (state.url !== '/login' && !this.authService.isLoggedIn()) {
this.router.navigate(['/login'], {queryParams: {redirectTo: state.url}});
return false;
}
return true;
}
}
import {Component} from "#angular/core";
import {ActivatedRoute, Router} from "#angular/router";
#Component({
templateUrl: "app/components/login/login.html"
})
export class LoginComponent {
constructor(public route: ActivatedRoute, public router: Router) { }
loginSuccessful() {
let redirect = this.route.snapshot.queryParams['redirect'];
let redirectUrl = redirect != null ? redirect : '/dashboard/';
console.log('redirect to: ' + redirect);
this.router.navigate([redirectUrl]);
}
}

Resources