I am trying to set the Response header in NestJS, but keep getting the following error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://companyName.okta.com/app/companyName_imentorlocalhost_1/exk1hp5ht4vrEzqGg0h8/sso/saml?SAMLRequest=nVPLct... (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
I tried setting the header in the controller, but that didn't work:
auth.controller:
#UseGuards(SamlAuthGuard)
#Header('Access-Control-Allow-Origin', '*')
#Get('box-utility-service/auth/login')
login(#Request() req): any {}
#UseGuards(SamlAuthGuard)
#Header('Access-Control-Allow-Origin', '*')
#Post('imentor-service/login/callback')
oktaCallback (#Request() req, #Response() res: Response): any {
return this.authService.login(req);
}
Also tried setting the header in an interceptor. Didn't work either:
header.interceptor.ts:
#Injectable()
export class HeaderInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap(() => {
const res = context.switchToHttp().getResponse();
res.setHeader('Access-Control-Allow-Origin', '*');
})
)
}
}
Here's my main.ts, where I enable CORS:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(
session({
secret: 'my-secret',
resave: false,
saveUninitialized: false
}),
);
app.enableCors({
allowedHeaders: [ 'Accept', 'Accept-Version', 'Content-Type', 'Api-Version', 'Origin', 'X-Requested-With',
'Authorization' ],
origin: [ 'https://companyName.okta.com', 'http://localhost:4200', 'http://localhost' ],
credentials: true,
exposedHeaders: [ 'API-Token-Expiry' ]
});
app.useGlobalInterceptors(new HeaderInterceptor());
await app.listen(3000);
}
bootstrap();
And here is my saml-strategy.ts file, where I define the Passport strategy to be SAML:
import { Injectable } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { AuthService } from './auth.service';
const nconf = require('nconf');
import { get } from 'lodash';
const SamlStrategy = require('passport-saml').Strategy;
import { UsersService } from '../users/users.service';
#Injectable()
export class Saml2Strategy extends PassportStrategy(SamlStrategy, 'saml') {
users = [];
constructor(
private authService: AuthService,
private usersService: UsersService
) {
super({
issuer: nconf.get('saml:issuer'),
path: nconf.get('saml:path'),
entryPoint: nconf.get('saml:entryPoint'),
cert: nconf.get('saml:cert')
});
}
async validate(payload: any) {
const oeid = payload.nameID;
let user;
if (oeid) {
try {
let userADData = await this.authService.validateUser(oeid);
userADData = get(userADData, 'data.data[0]');
if (userADData) {
user = await this.usersService.findOrCreate(userADData);
}
return user;
} catch (err) {
return err;
}
}
}
}
Any idea of what's going on? Thanks.
Turns out it was a problem with the way I was handling the OKTA login workflow. I was redirecting an XHR request (which OKTA didn't like, as it was missing the required header to do so), and was getting an error code.
Related
I'm writing here because I've lost my way on this problem. I've tried everything and every possible combination.
I'm working on course project with Angular. This is my first contact with Angular.
I've understood that what CORS is, why it is used and basically everything about it.
I've created my own Node.js server to work on. I've tried with and without installing cors package.
When installed I`ve provided all these different options (not at the same time of course):
This :
module.exports = () => (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
res.setHeader('Access-Control-Allow-Credentials', true);
res.setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.setHeader("Access-Control-Allow-Headers", 'Content-Type, X-Authorization');
next();
};
with :
The exported module above is named createdCors when imported
app.use(createdCors())
Same thing, but as direct middleware in index.js
app.use((req,res,next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'HEAD,OPTIONS,GET,POST,PUT,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Authorization');
next()
})
This is the cors package in index.js:
app.use(cors({
origin: 'http://localhost:4200',
credentials: true
}))
Another version
app.use(cors({credentials: true, origin: 'http://localhost:4200', allowedHeaders: ['Content-Type, X-Authorization, X-RapidAPI-Key, X-RapidAPI-Host']}))
Another
app.use(cors())
This is my angular interceptor :
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HTTP_INTERCEPTORS } from "#angular/common/http";
import { Injectable, Provider } from "#angular/core";
import { mergeMap, Observable, tap } from "rxjs";
#Injectable()
export class AppInterceptor implements HttpInterceptor{
accessToken:string | null = localStorage.getItem(`accessToken`)
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
if(this.accessToken)
{
return next.handle(req.clone({ setHeaders: {'X-Authorization' : this.accessToken}}))
}
else
{
return next.handle(req.clone())
}
}
}
export const AppInterceptorProvider: Provider = {
provide:HTTP_INTERCEPTORS,
useClass: AppInterceptor,
multi:true
}
This is my userService on the back-end:
function createAccessToken(user){
const payload = {
_id: user._id,
email: user.email,
username: user.username
}
const accessToken = jwt.sign(payload,SECRET_KEY)
return{
email: user.email,
username: user.username,
accessToken,
_id:user._id
}
}
async function register(username,email, password){
const existingEmail = await User.findOne({email})
const existingUser = await User.findOne({username})
if(existingEmail){
throw new Error(`Email is already taken`)
}
else if(existingUser) {
throw new Error(`User already exists`)
}
const user = await User.create({username,email,password})
return createAccessToken(user)
}
This is the register.ts :
import { Component } from '#angular/core';
import {FormBuilder, Validators } from '#angular/forms';
import { Router } from '#angular/router';
import { passwordValidator } from 'src/app/shared/validator';
import { AuthService } from '../auth.service';
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
errors: string | undefined = undefined;
constructor(private fb: FormBuilder, private userService: AuthService, private router: Router) {}
registerForm = this.fb.group({
email: [``,[Validators.required, Validators.email]],
username: [``, [Validators.required, Validators.minLength(6)]],
password: [``, [Validators.required, Validators.minLength(6)]],
rePass: [``, [Validators.required,passwordValidator]]
})
register(): void{
this.userService.register(this.registerForm.value).subscribe
({
next: () => this.router.navigate([`/`]),
error:(err)=> {
this.errors = err.error.error
}
})
this.registerForm.reset()
}
}
The values are properly taken from the form and all just this CORS error is not letting me go through.
I will be happy with any advice. I don`t want to install the chrome plugin for this. I want to make it properly.
Thanks
One of the possible scenarios is the target URL is not using https, to be sure if that's the case open the browser as administrator:
[1]: https://i.stack.imgur.com/HP7SV.png
But first check all the parameters of httpClient request, and here you can find an example for inspiration
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class DataService {
googleUrl = 'https://www.googleapis.com/books/v1/volumes';
constructor(private httpClient: HttpClient) { }
getBooks(): Observable<object> {
let httpHeaders = new HttpHeaders()
.set('Accept', 'application/json')
let httpParams = new HttpParams()
.set('q', 'Gabriel García');
return this.httpClient.get<object>(this.googleUrl, {
headers: httpHeaders,
params: httpParams,
responseType: 'json'
});
}
}
One of the solutions is to add proxy.conf.json in the root of the project.
Here you can find a good description:
https://levelup.gitconnected.com/fixing-cors-errors-with-angular-cli-proxy-e5e0ef143f85
Im currently trying to learn more about Express and Express-Validator but currently facing the following issue: When I'm starting the server and using Postman do excess one of the endpoints the response is not completed. However, it seems like the Validation Chain is being processed but afterwards nothing happens. I have the following modules:
index.ts
import {config} from 'dotenv';
import App from './api/app';
import ControlAmbilight from './api/routes/controlAmbilight';
import AdjustLightning from './app/AdjustLightning';
const ENV_FILE = path.join(__dirname, '..', '.env');
config({path: ENV_FILE});
const PORT = process.env.port || process.env.PORT || 3000;
const ambient = new AdjustLightning();
const app = new App([
new ControlAmbilight(ambient),
], <number> PORT);
app.listen();
app.ts
import * as bodyParser from 'body-parser';
import pino from 'pino';
import expressPino from 'express-pino-logger';
import errorMiddleware from './middleware/errorMiddleware';
export default class App {
private logger: pino.Logger;
private expressLogger;
public app: express.Application;
public port: number;
constructor(controllers: any, port: number) {
this.app = express();
this.port = port;
this.logger = pino({level: process.env.LOG_LEVEL || 'info'});
this.expressLogger = expressPino({logger: this.logger});
this.initializeMiddlewares();
this.initializeControllers(controllers);
this.initializeErrorHandling();
}
private initializeMiddlewares() {
this.app.use(this.expressLogger);
this.app.use(bodyParser.json());
}
private initializeControllers(controllers: any) {
controllers.forEach((controller: any) => {
this.app.use('/', controller.router);
});
}
private initializeErrorHandling() {
this.app.use(errorMiddleware)
}
public listen() {
this.app.listen(this.port, () => {
this.logger.info(`Server running on ${this.port}`);
});
}
}
controlAmbilight.ts
import {Router, Request, Response, NextFunction} from 'express';
import {ValidationChain, check, validationResult} from 'express-validator';
import AdjustLightning from '../../app/AdjustLightning';
// eslint-disable-next-line require-jsdoc
export default class ControlAmbilight {
private ambient: AdjustLightning;
// eslint-disable-next-line new-cap
public router = Router();
public path = '/controlAmbilight';
// eslint-disable-next-line require-jsdoc
constructor(ambient: AdjustLightning) {
this.ambient = ambient;
this.initializeRoutes();
}
// eslint-disable-next-line require-jsdoc
public initializeRoutes() {
this.router.post(this.path, this.controlValidator, this.setAmbilight.bind(this));
}
private controlValidator = (): ValidationChain[] => [
check('on').notEmpty().withMessage('Field \'on\' is required'),
check('on').isBoolean().withMessage('Field \'on\' must be type boolean'),
];
// eslint-disable-next-line require-jsdoc
private setAmbilight(req: Request, res: Response): void {
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.status(422).json({error: errors.array()});
} else {
const isOn = (req.body.on == 'true');
res.send(`The curent state is: ${this.ambient.getIsActive()}`);
}
}
}
I was hopping that someone could explain me what I'm missing here. It seems like I need to call express` next() middleware function, but I'm not sure where to implement it.
EDIT
As requested I'm adding the errorMiddleware:
import { NextFunction, Request, Response } from 'express';
import HttpException from '../exceptions/HttpException';
export default function errorMiddleware (error: HttpException,
request: Request, response: Response, next: NextFunction) {
const status = error.status || 500;
const message = error.message || 'Ups... This did not work :(';
response
.status(status)
.send({ status,
message });
}
And as an additional comment: When I'm adding the Validation Chain directly into the post method within controlAmbilight.ts like that:
public initializeRoutes() {
this.router.post(this.path, [
check('on').notEmpty().withMessage('Field \'on\' is required'),
check('on').isBoolean().withMessage('Field \'on\' must be type boolean'),
], this.setAmbilight.bind(this));
}
It is working as expected.
I am very new to typescript/javascript, I am trying to build backend rest apis with session
following is app.ts file
import express from "express";
import { applyMiddleware, applyRoutes } from "./utils";
import routes from "./services";
const app = express();
var ses= {
secret: "secret_session",
resave: true,
saveUninitialized: true,
cookie: { maxAge: 3600000,secure: false, httpOnly: true
}
if (app.get('env') === 'production') {
app.set('trust proxy', 1)
ses.cookie.secure = true
}
app.use(session(ses));
applyRoutes(routes, app);
I have started the server and applied the middlewares for error handling but those are not related to question in my opinion so I'm not adding code for it. Following is my routes.ts code where I'm trying to set the session.
import { Request, Response } from "express";
import { getAll, getByKeyword, addNewProduct } from "./productControllers";
{
path: "/api/v1/getAllProducts",
method: "get",
handler: [
(req: Request, res: Response) => {
getAll()
.then((row: any) => {
var sess = req.session;
sess.views = 1;
res.status(200).json({ data: row });
})
.catch(err => {
res.json({
message: err
});
});
}
]
}
I'm getting error at sess.views = 1;
I have tried the suggested questions before asking it, none of them were of any help to me.
EDIT:
I have created an index.ts
import searchRoutes from "./products/routes";
export default [...searchRoutes];
I have another util class
export const applyRoutes = (routes: Route[], router: Router) => {
for (const route of routes) {
const { method, path, handler } = route;
(router as any)[method](path, handler);
}
}
You are using an interface which is Request for express.js. But it doesn't have type definition for session. So typescript throws a compile error. To solve it you need to define session type under Request interface.
You could define a session.d.ts file under your project. And create required types & interfaces. Like:
declare global {
namespace Express {
interface Request {
session?: Session;
sessionID?: string;
}
}
}
interface Session{
mySessionVarible:string
}
But the good thing is we have DefinitilyTyped project which you can find many type definitions. This needs to solve your compile problem.
npm install --save-dev #types/express-session
And don't forget to change your import for Request.
import { Request, Response } from "#types/express-session";
I need to send from my server side to API in request headers "Cookie: token"
In angular universal, for servers Http request methods I use axios. when I try to change headers using Interceptor I have an error "Refused to set unsafe header 'Cookie'" if I send a static cookie like third arguments of axios post all work fine but I have some troubles to dynamically insert token there.
request.servise.ts
import { RequestBody } from './models/request-body.model';
import axios from 'axios';
import {AxiosInstance} from 'axios';
export class Request {
http: AxiosInstance = axios;
constructor() {}
async test(): Promise<any> {
const params = {
param1: 1,
param2: 2,
param3: 3,
}
try {
return this.basicRequest('https://some-request.url', params);
} catch (e) {
console.error('Unknown exception: ', e);
return null;
}
}
private basicRequest(url, params) {
const request = new RequestBody('2.0', 'someMethod', Math.floor(Math.random() * (9999999 - 1000000)) + 1000000, params);
return this.http.post(url, request);
}
}
projects.ts (sever router controller)
import {Router} from 'express';
import {Request} from '../../shared/request.service';
const router: Router = Router();
router.get('/test', async function(req, res, next){
const request = new Request();
try {
const projects = (await request.test()).data.result.records;
res.json(projects);
} catch (e) {
console.error(e);
}
});
export const ProjectsController: Router = router;
By default, angular universal does not transfert cookieswhen using HttpClient. So, you need to do it manually, but you'll get the error you mentionned.
A possible workaround, suggested in that universal github issue, is to bypass xhr2's default security behaviour for unsafe headers
server.ts
import * as xhr2 from 'xhr2';
xhr2.prototype._restrictedHeaders = {};
I have the following express server:
import * as express from "express";
import * as bodyParser from "body-parser";
import * as mongoose from "mongoose";
import { Routes } from "./routes/transactions";
import { authMiddleware } from "./middleware/auth";
import { errorMiddleware } from "./middleware/error";
class App {
public app: express.Application;
public routeProvider: Routes = new Routes();
public mongoUrl: string = "mongodb://...:...#mydomain.com:27017/mycollection"; // This should be read from github CLI
constructor() {
this.app = express();
this.config();
this.routeProvider.routes(this.app);
this.mongoSetup();
}
private config(): void {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: false }));
this.app.use(errorMiddleware);
this.app.use(authMiddleware);
}
private mongoSetup(): void {
mongoose.Promise = global.Promise;
mongoose.connect(
this.mongoUrl,
{ useNewUrlParser: true }
);
}
}
export default new App().app;
This is the authMiddleware (almost the same as the guide on the auth0 site for usage with node.js):
import * as jwt from "express-jwt";
import { expressJwtSecret } from "jwks-rsa";
export function authMiddleware() {
return jwt({
secret: expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: "..."
}),
audience: "...",
issuer: "...",
algorithms: ["RS256"]
});
}
And my error middleware:
import { Request, Response } from "express";
export function errorMiddleware(err, req: Request, res: Response, next) {
console.error(err.stack);
res.status(500).send("Something broke!");
}
Now what would expect to happen - since I broke all the auth and mongodb URLs on purpose, that I would get a status code 500 with the message Something broke!.
Instead I get the
Could not get any response
message when sending a requst using postman.
What am I doing wrong?