I am trying to used Azure AD to integrate my application, but I keep getting this error
AuthError.ts:49 Uncaught (in promise) BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API. For more visit: aka.ms/msaljs/browser-errors.
at BrowserAuthError.AuthError [as constructor] (AuthError.ts:49:1)
at new BrowserAuthError (BrowserAuthError.ts:195:1)
at BrowserAuthError.createInteractionInProgressError (BrowserAuthError.ts:276:1)
at BrowserCacheManager.setInteractionInProgress (BrowserCacheManager.ts:1000:1)
at ClientApplication.preflightInteractiveRequest (ClientApplication.ts:837:1)
at ClientApplication.preflightBrowserEnvironmentCheck (ClientApplication.ts:820:1)
at PublicClientApplication.<anonymous> (ClientApplication.ts:272:1)
at step (vendors~main.chunk.js:217:19)
at Object.next (vendors~main.chunk.js:147:14)
at vendors~main.chunk.js:119:67
at new Promise (<anonymous>)
at __awaiter (vendors~main.chunk.js:98:10)
at ClientApplication.acquireTokenRedirect (ClientApplication.ts:268:1)
at index.tsx:50:1
This is my index.tsx file:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import '#scuf/common/honeywell/theme.css';
import '#scuf/datatable/honeywell/theme.css';
import store from './stores';
import { Provider } from 'mobx-react';
import createRouter from './router';
import './index.scss';
import { msalConfig } from "./stores/authConfig";
import { MsalProvider, MsalAuthenticationTemplate } from "#azure/msal-react";
import { InteractionRequiredAuthError, AuthError } from "#azure/msal-common";
import { PublicClientApplication, InteractionType } from "#azure/msal-browser";
const msalInstance = new PublicClientApplication(msalConfig);
msalInstance.handleRedirectPromise()
.then((redirectResponse) => {
if (redirectResponse !== null) {
// Acquire token silent success
let accessToken = redirectResponse.accessToken;
console.log(accessToken)
// Call your API with token
} else {
// MSAL.js v2 exposes several account APIs, logic to determine which account to use is the responsibility of the developer
const activeAccount = msalInstance.getActiveAccount();
const accounts = msalInstance.getAllAccounts();
if (!activeAccount && accounts.length === 0) {
console.error("User not logged in!!!");
}
const accessTokenRequest = {
scopes: ["user.read", "openid"],
account: activeAccount || accounts[0],
// roles: ["rca.approver"],
};
msalInstance
.acquireTokenSilent(accessTokenRequest)
.then(function (accessTokenResponse) {
// Acquire token silent success
// Call API with token
let accessToken = accessTokenResponse.accessToken;
console.log(accessToken)
// Call your API with token
})
.catch(function (error) {
//Acquire token silent failure, and send an interactive request
console.log(error);
if (error instanceof InteractionRequiredAuthError || error instanceof AuthError) {
msalInstance.acquireTokenRedirect(accessTokenRequest);
}
});
}
})
// Here we are importing our stores file and spreading it across this Provider. All stores added to this will be accessible via child injects
const wrappedApp = (
<MsalProvider instance={msalInstance}>
<MsalAuthenticationTemplate interactionType={InteractionType.Redirect}>
<Provider store={store}>
<App />
</Provider>
</MsalAuthenticationTemplate>
</MsalProvider>
);
// Here the router is bootstrapped
const router = createRouter();
router.start(() => {
ReactDOM.render(wrappedApp, document.getElementById('root') as HTMLElement);
});
and this is my authConfig.js:
export const msalConfig = {
auth: {
clientId: "XXX",
authority: "https://login.microsoftonline.com/YYY",
redirectUri: "http://localhost:3000",
postLogoutRedirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
export const loginRequest = {
scopes: ["User.Read","openid"],
redirectUri: "http://localhost:3000",
};
// Add the endpoints here for Microsoft Graph API services you'd like to use.
export const graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me"
};
I have tried the solution in net but it still gives me the same error. These are the only two files in my project folder that deals with MSAL packages. Did I miss anything? As I learnt from the documenatation, interactionType redirects to AD authentication on which token is generated which could then be sent to APIs. Please correct me if I am wrong.
Related
I'm looking at this page https://docs.aws.amazon.com/sdk-for-javascript... and it seems to imply that the SDK will look at ~/.aws/credentials and take the [default] profile if there is no AWS_PROFILE environment var.
I'm running a NextJS app on my local machine trying to list S3 buckets, getting Error: Credential is missing.
I would really love not to have to specify the creds in env vars as I'll be deploying the app to ECS later where it will use an IAM Role for access.
Here's my code:
import { ListBucketsCommand } from '#aws-sdk/client-s3';
import React, { useEffect } from 'react';
import { s3Client } from '../lib/s3Client';
const S3Buckets = () => {
useEffect(() => {
async function getS3Buckets() {
const input = {};
const command = new ListBucketsCommand(input);
const res = await s3Client.send(command);
console.log(res);
}
getS3Buckets();
}, []);
return <div>{/* S3 Buckets eventually listed here */}</div>;
};
export default S3Buckets;
with the s3Client helper as below:
import { S3Client } from '#aws-sdk/client-s3';
export const s3Client = new S3Client({ region: process.env.AWS_REGION });
I am currently trying to create a middleware that will user's data by fetching an external endpoint with Axios. Axios too, does not work with the middleware. Here is my error when using node-fetch:
Module build failed: UnhandledSchemeError: Reading from "node:buffer" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
Any idea why this happens?
Here is my code:
import fetch from "node-fetch"
import { getSession } from "next-auth/react";
import { NextMiddleware, NextResponse } from "next/server";
export const middleware: NextMiddleware = async (req, ev) => {
const session = await getSession() as any;
const user = await fetch("some-url", {
headers: {
Authorization: `Bearer ${session?.user?.someproperty}`,
},
});
if (user.statusCode !== 200) return NextResponse.redirect(req.url);
else return NextResponse.next();
};
In my case I was using axios inside middleware, and got the following error
error - node_modules\axios\lib\core\dispatchRequest.js (53:0) # dispatchRequest
TypeError: adapter is not a function
I had to install axios-fetch-adapter according to this question
My code would like
import type { NextFetchEvent, NextRequest } from 'next/server'
import fetchAdapter from '#vespaiach/axios-fetch-adapter'
import axios from 'axios'
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
const axiosInstance = axios.create({
adapter: fetchAdapter
})
const data = await axiosInstance.get('/whatever-url')
// the rest of the code
}
Or if you already had a preconfigured instance with custom options
import type { NextFetchEvent, NextRequest } from 'next/server'
import fetchAdapter from '#vespaiach/axios-fetch-adapter'
import axiosInstance from './the-path-of-your-instance'
export async function middleware(req: NextRequest, ev: NextFetchEvent) {
axiosInstance.defaults.adapter = fetchAdapter
const data = await axiosInstance.get('/whatever-url')
// the rest of the code
}
I'm familiar with Svelte but completely new to Sveltekit. I'm trying to build a Sveltekit app from scratch using AWS Cognito as the authorization tool without using AWS Amplify, using the amazon-cognito-identity-js sdk. I've got all the functionality working as far as login, registration, and verification, but I can't seem to get a handle on how to store the session data for the structure I've built.
I've been trying to translate the strategies from this tutorial, based in React, to Sveltekit -- (AWS Cognito + React JS Tutorial - Sessions and Logging out (2020) [Ep. 3]) https://www.youtube.com/watch?v=R-3uXlTudSQ
and this REPL to understand using context in Svelte ([AD] Combining the Context API with Stores) https://svelte.dev/repl/7df82f6174b8408285a1ea0735cf2ff0
To elaborate, I've got my structure like so (only important parts shown):
src
|
|-- components
|-- ...
|-- status.svelte
|-- routes
|
|-- dashboard
|-- onboarding
|-- __layout.reset.svelte
|-- login.svelte
|-- signup.svelte
|-- verify.svelte
|-- ...
|-- settings
|-- __layout.svelte
|-- index.svelte
|-- styles
|-- utils
|-- cognitoTools.ts
|-- stores.ts
I wanted to have a separate path for my onboarding pages, hence the sub-folder. My cognito-based functions reside within cognitoTools.ts. An example of a few functions look like:
export const Pool = new CognitoUserPool(poolData);
export const User = (Username: string): any => new CognitoUser({ Username, Pool });
export const Login = (username: string, password: string): any => {
return new Promise ((resolve, reject) => User(username).authenticateUser(CognitoAuthDetails(username, password), {
onSuccess: function(result) {
console.log('CogTools login success result: ', result);
resolve(result)
},
onFailure: function(err) {
console.error('CogTools login err: ', err);
reject(err)
}
}))
}
I'm able to then use the methods freely anywhere:
// src/routes/onboarding/login.svelte
import { Login, Pool } from '#utils/cognitoTools'
import { setContext, getContext } from 'svelte'
let username;
let password;
let session = writeable({});
let currentSession;
// Setting our userSession store to variable that will be updated
$: userSession.set(currentSession);
// Attempt to retrieve getSession func defined from wrapper component __layout.svelte
const getSession = getContext('getSession');
const handleSubmit = async (event) => {
event.preventDefault()
Login(username, password, rememberDevice)
.then(() => {
getSession().then((session) => {
// userSession.set(session);
currentSession = session;
setContext('currentSession', userSession);
})
})
}
...
// src/routes/__layout.svelte
...
const getSession = async () => {
return await new Promise((resolve, reject) => {
const user = Pool.getCurrentUser();
if (user) {
user.getSession((err, session) => {
console.log('User get session result: ', (err ? err : session));
err ? reject() : resolve(session);
});
} else {
console.log('get session no user found');
reject();
}
})
}
setContext('getSession', getSession)
Then, I've been trying to retrieve the session in src/components/status.svelte or src/routes/__layout.svelte (as I think I understand context has to be set in the top level components, and can then be used by indirect child components) to check if the context was set correctly.
Something like:
let status = false;
const user = getContext('currentSession');
status = user ? true : false;
I'm running in circles and I know I'm so close to the answer. How do I use reactive context with my current file structure to accomplish this?
I don't know much about the sdk, so I can't help you with your code above. But I also built an app that uses cognito for auth, and I can share some snippets on how to do it from scratch.
Implement a login form. I have my basic app skeleton (navbar, footer, main slot) in _layout.svelte, and it is configured to show the Login.svelte component and not the main slot if the user is not logged in.
file: __layout.svelte
<script context="module">
export async function load({ session }) {
return {
props: {
user: session.user,
}
}
}
</script>
<script>
import "../app.css";
import Login from "$components/Login.svelte";
export let user
</script>
<svelte:head>
<title>title</title>
</svelte:head>
{#if user}
<header>
</header>
<main>
<slot />
</main>
{:else}
<Login />
{/if}
file: Login.svelte
<form action="/" method="GET">
<input type="hidden" name="action" value="signin" />
<button type="submit" >Sign in</button>
</form>
Handle the login
I choose to do this as a svelte endpoint paired with the index. It keeps the routing super simple. You could do separate login.js and logout.js endpoints if you prefer. Just change your url in the form above.
file: index.js
import { v4 as uuid } from '#lukeed/uuid'
import db from '$lib/db'
const domain = import.meta.env.VITE_COGNITO_DOMAIN
const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID
const redirectUri = import.meta.env.VITE_COGNITO_REDIRECT_URI
const logoutUri = import.meta.env.VITE_COGNITO_LOGOUT_URI
export const get = async (event) => {
const action = event.url.searchParams.get('action')
if (action === 'signin') {
// Hard to guess random string. Used to protect against forgery attacks.
// Should add check in callback that the state matches to prevent forgery
const state = uuid()
return {
status: 302,
headers: {
location: `https://${domain}/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=openid+email+profile&state=${state}`,
},
}
}
if (action === 'signout') {
// delete this session from database
if (event.locals.session_id) {
await db.sessions.deleteMany({
where: { session_id: event.locals.session_id }
})
}
return {
status: 302,
headers: {
location: `https://${domain}/logout?client_id=${clientId}&logout_uri=${logoutUri}`
}
}
}
return {}
}
Handle the callback from AWS cognito. Again, I have the callback simply point to the root url. All authentication for me is handled at "/". The heavy lifting is done by hooks.js. This is your SK middleware. It for me is the sole arbiter of the user's authentication state, just because I like to keep it easy for me to understand.
file: hooks.js
import { v4 as uuid } from '#lukeed/uuid'
import cookie from 'cookie'
import db from '$lib/db'
const domain = import.meta.env.VITE_COGNITO_DOMAIN
const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID
const clientSecret = import.meta.env.VITE_COGNITO_CLIENT_SECRET
const redirectUri = import.meta.env.VITE_COGNITO_REDIRECT_URI
const tokenUrl = `https://${domain}/oauth2/token`
const profileUrl = `https://${domain}/oauth2/userInfo`
export const handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '')
event.locals.session_id = cookies.session_id // this will be overwritten by a new session_id if this is a callback
if (event.locals.session_id) {
// We have a session cookie, check to see if it is valid.
// Do this by checking against your session db or session store or whatever
// If not valid, or if it is expired, set event.locals.session_id to null
// This will cause the cookie to be deleted below
// In this example, we just assume it's valid
}
if ( (!event.locals.session_id) && event.url.searchParams.get('code') && event.url.searchParams.get('state') ) {
// No valid session cookie, check to see if this is a callback
const code = event.url.searchParams.get('code')
const state = event.url.searchParams.get('state')
// Change this to try, catch for error handling
const token = await getToken(code, state)
if (token != null) {
let cognitoUser = await getUser(token)
event.locals.session_id = uuid()
// Add the value to the db
await db.sessions.create({
data: {
session_id: event.locals.session_id,
user_id: cognitoUser.username,
created: Date()
},
})
let user = await db.users.findUnique({
where: {
user_id: cognitoUser.username,
}
})
event.locals.user = user
event.locals.authorized = true
}
}
const response = await resolve(event);
// This will delete the cookie if event.locals.session_id is null
response.headers.set(
'set-cookie',
cookie.serialize('session_id', event.locals.session_id, {
path: '/',
httpOnly: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24 * 7, // one week
})
)
return response;
}
export async function getSession(event) {
return {
user: event.locals.user,
}
}
const getToken = async (code, state) => {
let authorization = Buffer.from(`${clientId}:${clientSecret}`).toString('base64')
const res = await fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${authorization}`,
},
body: `grant_type=authorization_code&client_id=${clientId}&code=${code}&state=${state}&redirect_uri=${redirectUri}`
})
if (res.ok) {
const data = await res.json()
return data.access_token
} else {
return null
}
}
const getUser = async (token) => {
const res = await fetch(profileUrl, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (res.ok) {
return res.json()
} else {
return null
}
}
Lastly, getting the auth state. In a client-side route, this is done via the page load function. I put this in __layout to have it available on all .svelte routes. You can see it at the top of the __layout file above. For SSR endpoints, you can just access event.locals directly.
NOTE: All of the env vars are set in your .env file and will be imported by vite. This only happens when you start your app, so if you add/change them, you need to restart it.
I don't know if this helps at all since it is so different from your app structure, but maybe you will get some ideas from it.
I don't know what the problem is you run into exactly, but one thing that stands out to me is that you are calling setContext when it's "too" late. You can only call getContext/setContext within component initialization. See this answer for more details: Is there a way to use svelte getContext etc. svelte functions in Typescript files?
If this is the culprit and you are looking for a way how to get the session then: Use context in combination with stores:
<!-- setting the session -->
<script>
// ...
const session = writable(null);
setContext('currentSession', session);
// ...
Login...then(() => ...session.set(session));
</script>
<!-- setting the session -->
<script>
// ..
const user = getContext('currentSession');
// ..
status = $user ? true : false;
</script>
Another thing that stands out to me - but is too long/vague for a StackOverflow answer - is that you are not using SvelteKit's features to achieve this behavior. You could look into load and use stuff in __layout to pass the session down to all children. I'm no sure if this is of any advantage for you though since you are maybe planning to do a SPA anyway and therefore don't need such SvelteKit features.
the problem
I created a Shopify node.js app using the Shopify CLI and I want to display a simple bar under the header using a script tag. I used the script tag API to add a script tag
"script_tags": [
{
"id": 174240039086,
"src": "https://xxxxx.ngrok.io/script_tag",
}
]
And I also added a <div id="script-app"></div> into the theme, under the header.
Here is my script_tag.js file, located in /pages/script_tag.js
import ReactDOM from 'react-dom';
class TestScriptTag extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
<p>this is a bar</p>
</div>
);
}
}
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
export default TestScriptTag;
Lastly, here is my server.js (most of it is what came with the CLI):
import "#babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "#shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "#shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import { flushSync } from "react-dom";
const fs = require('fs');
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8083;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: false,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
console.log("here")
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.get("/", async (ctx) => {
const shop = ctx.query.shop;
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
router.get("/script_tag", (ctx) => {
handleRequest(ctx);
});
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(.*)", verifyRequest(), handleRequest); // Everything else must have sessions
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I am getting the error: document not defined.
What I've tried
I thought this is due to server side rendering, so I thought I could get around it by doing this:
if (typeof window !== "undefined") {
ReactDOM.render(<TestScriptTag />, document.getElementById('script-app'));
}
But still nothing renders and I get this when I inspect the shop page.
I've also tried changing the routing to this:
router.get("/script_tag", (ctx) => {
ctx.type = "module";
ctx.body = fs.createReadStream('./pages/script_tag.js')
});
But then I get an error about the import statement in script_tag.js - SyntaxError: Unexpected identifier '{'. import call expects exactly one argument.
I'm not sure what the proper way is to serve the javascript file I want to inject into the header. I feel like I'm missing something stupid. Please help!!
I'm new to FeathersJS. I tried to set up OAuth login with Twitch. I created a twitch oauth application and did the same as here with github login. I want to save the user in my MongoDB database, but I'm getting redirected to http://localhost:3030/#error=401%20Unauthorized after logging in on twitch. I've generated a fresh application with the feathers cli. What am I doing wrong?
config/default.json
...
"oauth": {
"redirect": "/",
"twitch": {
"key": ""*****************",",
"secret": ""***************************************"",
"scope": ["user:read:email"]
},
"github": {
"key": "*****************",
"secret": "***************************************"
}
}...
This is because the built-in strategy is attempting to fetch the user profile, yet it is not including your Client ID in the headers (see Twitch API - Getting Started.
To solve this you need to create your own strategy that implements getProfile. I used the Facebook demo from the feathersjs cookbook as a reference which can be found here.
Here is my implementation:
./strategies/TwitchStrategy.ts
import { Params } from '#feathersjs/feathers'
import { AuthenticationRequest } from '#feathersjs/authentication'
import { OAuthStrategy, OAuthProfile } from '#feathersjs/authentication-oauth'
import axios from 'axios'
import { Application } from '../declarations'
export class TwitchStrategy extends OAuthStrategy {
// we need a reference to the app instance
app: Application = {} as Application
// when the strategy is initialized this method is called with an app instance
setApplication(appInstance: Application): void {
this.app = appInstance
}
// this method is used to get the user profile after they authorize with the provider
async getProfile(authResult: AuthenticationRequest, _params: Params) {
const accessToken = authResult.access_token
const { data } = await axios.get('https://api.twitch.tv/helix/users', {
headers: {
Authorization: `Bearer ${accessToken}`, //our users access token to look them up
'Client-ID': this.app.get('authentication').oauth.twitch.key //we need to send the Client-ID
},
params: {
fields: 'id,name,email'
}
})
console.log(data)
return data
}
async getEntityData(profile: OAuthProfile, existing: any, params: Params) {
// `profile` is the data returned by getProfile
const baseData = await super.getEntityData(profile, existing, params)
return {
...baseData,
email: profile.email
}
}
}
./authentication.ts
import { ServiceAddons } from '#feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '#feathersjs/authentication'
import { LocalStrategy } from '#feathersjs/authentication-local'
// import our strategy
import { TwitchStrategy } from './strategies/TwitchStrategy'
import { expressOauth } from '#feathersjs/authentication-oauth'
import { Application } from './declarations'
declare module './declarations' {
interface ServiceTypes {
authentication: AuthenticationService & ServiceAddons<any>
}
}
export default function (app: Application): void {
const authentication = new AuthenticationService(app)
authentication.register('jwt', new JWTStrategy())
// register our custom strategy
authentication.register('twitch', new TwitchStrategy())
authentication.register('local', new LocalStrategy())
app.use('/authentication', authentication)
app.configure(expressOauth())
}