Microsoft Graph API - Create user in Azure AD B2C with local account identity gives Request_ResourceNotFound error - azure-active-directory

My goal is to create a local account, like michele#gmail.com, in Azure Active Directory B2C.
I want to use my own UI, so I started to look at how to implement the API. After some research, it looks like the best way to do it should be via Microsoft Graph.
I started by following Manage Azure AD B2C user accounts with Microsoft Graph, and I properly created an app (not sure if I need to choose the third options, but looks like the broader one):
with the following permissions (updated following also #Tony Ju screenshot):
Then I created the client secret and coded my own auth provider
const { AuthenticationContext } = require('adal-node');
class AuthProvider {
async getAccessToken() {
return new Promise((resolve, reject) => {
const tenant = 'tenant';
const authority = `https://login.microsoftonline.com/${tenant}`;
const authenticationContext = new AuthenticationContext(authority);
const resource = 'https://graph.microsoft.com';
const clientId = 'clientId';
const clientSecret = 'clientSecret';
authenticationContext.acquireTokenWithClientCredentials(
resource,
clientId,
clientSecret,
(err, tokenResponse) => {
if (err) {
console.error('error!', err);
return reject(err);
}
return resolve(tokenResponse.accessToken);
},
);
});
}
}
and initialized the client
require('isomorphic-fetch'); //needed for server side request with the client
const { Client } = require('#microsoft/microsoft-graph-client');
const options = {
authProvider: new AuthProvider(),
};
const client = Client.initWithMiddleware(options);
Following the official documentation, I create a local account
const user = {
displayName: 'John Smith',
identities: [
{
signInType: 'emailAddress',
issuer: 'MY_ISSUER.onmicrosoft.com',
issuerAssignedId: 'jsmith#yahoo.com',
},
],
passwordProfile: {
password: 'df42bfe2-8060-411f-b277-06b819874573',
},
passwordPolicies: 'DisablePasswordExpiration',
};
client
.api('/users')
.post(user)
.then(data => console.log(data))
.catch(e => console.error(e))
And I get back this "Request_ResourceNotFound" error
GraphError {
statusCode: 404,
code: 'Request_ResourceNotFound',
message:
'Resource \'User_30140fa1-ae7e-40b7-ad5a-ef4d0b4cd4dc\' does not exist or one of its queried reference-property objects are not present.',
requestId: 'fbf4c987-0383-472a-bc22-c94f98710344',
date: 2020-05-18T13:19:14.000Z,
body:
'{"code":"Request_ResourceNotFound","message":"Resource \'User_30140fa1-ae7e-40b7-ad5a-ef4d0b4cd4dc\' does not exist or one of its queried reference-property objects are not present.","innerError":{"request-id":"fbf4c987-0383-472a-bc22-c94f98710344","date":"2020-05-18T15:19:14"}}' }
The error does not help and I don't know how to continue. The basic configuration looks correct, because I'm able to get all the users and also create a user in the same tenant.
What am I missing? Feels so weird having this type of error by following the official docs. I'm starting to think that I need to use the invitation API, but I just want to create a user and don't go into the full email verification flow. Plus, what I exactly need is in the official docs and I would expect it to work. So maybe there is just something wrong on my side.

Your code works perfectly. I only updated tenant,clientId,clientSecret and MY_ISSUER in your code. Here is the whole code I ran.
const { AuthenticationContext } = require('adal-node');
class AuthProvider {
async getAccessToken() {
return new Promise((resolve, reject) => {
const tenant = '6b72a356-867d-4c35-bde6-959d99388ca8';
const authority = `https://login.microsoftonline.com/${tenant}`;
const authenticationContext = new AuthenticationContext(authority);
const resource = 'https://graph.microsoft.com';
const clientId = '270408b5-85a6-4e99-861e-7634853a5827';
const clientSecret = 'VYD_F_Rr~eHYqLtTXqa1~1KRS_932yNw35';
authenticationContext.acquireTokenWithClientCredentials(
resource,
clientId,
clientSecret,
(err, tokenResponse) => {
if (err) {
console.error('error!', err);
return reject(err);
}
return resolve(tokenResponse.accessToken);
},
);
});
}
}
require('isomorphic-fetch'); //needed for server side request with the client
const { Client } = require('#microsoft/microsoft-graph-client');
const options = {
authProvider: new AuthProvider(),
};
const client = Client.initWithMiddleware(options);
const user = {
displayName: 'John Smith',
identities: [
{
signInType: 'emailAddress',
issuer: 'tonyb2ctest.onmicrosoft.com',
issuerAssignedId: 'jsmith4#yahoo.com',
},
],
passwordProfile: {
password: 'df42bfe2-8060-411f-b277-06b819874573',
},
passwordPolicies: 'DisablePasswordExpiration',
};
client
.api('/users')
.post(user)
.then(data => console.log(data))
.catch(e => console.error(e))
The result:
I registered an application under Azure Active Directory and granted Directory.ReadWrite.All permission.

Related

onAuthStateChanged and getRedirectResult returning null user after successful google login redirect

Somewhat randomly my login flow stops working for some users when using google as a login provider (possibly facebook too, unsure) on iOS. They are able to log in on desktop using google login with the same app bundle. I think this is happening for ~1/10 users.
After a user selects which google account to use, the google redirects back to my app. The onAuthStateChange triggers, but the user is null. This is after successfully "logging in" with google. I think this may have to do with users trying to use multiple auth providers and getting stuck in a weird state. I'm not receiving any error or console logs when this happens, it's as if it's a fresh page load instead of a redirect.
I haven't been able to reproduce the issue using my own account until recently. I tried reverting back to earlier builds when the issue wasn't present for my account, with no luck. I'm now unable to log into my own app 😂
in UserProvider.tsx:
firebaseConfig.apiKey = process.env.REACT_APP_AUTH_API_KEY;
firebaseConfig.authDomain = process.env.REACT_APP_AUTH_DOMAIN;
firebaseConfig.projectId = "redacted";
firebaseConfig.storageBucket = "redacted.appspot.com";
firebaseConfig.messagingSenderId = "redacted";
firebaseConfig.appId = "redacted";
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firebase Authentication and get a reference to the service
const auth = initializeAuth(app, {
popupRedirectResolver: browserPopupRedirectResolver,
persistence: [indexedDBLocalPersistence, browserLocalPersistence, inMemoryPersistence],
errorMap: debugErrorMap
});
const UserContextProvider = (props) => {
const signInWithGoogle = () => {
signInWithRedirect(auth, googleAuthProvider);
}
const handleUserCredential = async (result: UserCredential) => {
if (result.user) {
const additionalInfo = getAdditionalUserInfo(result)
dispatch({
type: 'SET_ADDITIONAL_USER_INFO', data: {
additionalInfo: additionalInfo,
}
});
}
}
useEffect(() => {
dispatch({ type: 'LOGIN_INITIATED' });
const handleRedirectResult = async () => {
alert("handling redirect result");
const result = await getRedirectResult(auth);
alert(`redirect result: ${result}`);
if (result) {
handleUserCredential(result)
}
}
onAuthStateChanged(auth, async (user: User | null) => {
alert(`on auth change fired: ${user}`);
if (user) {
// set the token now
const token = await getIdToken(user);
const refreshToken = async () => {
return await getIdToken(user, true);
}
onLoginSuccess(user, token, refreshToken);
} else {
onLogoutSuccess();
}
}, (error) => {
onError(error)
});
handleRedirectResult();
}, [])
onLogoutSuccess() is triggering because user is null.
PS: I spammed a bunch of alerts in there because I'm struggling to debug on my device, those aren't in my production build.

NextJS-Auth0: How can I assign a role on signup with Auth0?

Using the library nextjs-auth0 (https://github.com/auth0/nextjs-auth0) I've been trying to make use of the handleAuth hook to grab a query arg to specify which role should be assigned to the user on signup.
An example of what I'm trying to do:
//pages/api/auth/[...auth0].js
const getLoginState = (req, loginOptions) => {
const { role } = req.query;
return { role: role };
};
export default handleAuth({
async login(req, res) {
try {
await handleLogin(req, res, { getLoginState } );
} catch (error) {
res.status(error.status || 500).end(error.message);
}
}
});
The documentation for handleAuth makes it seem like it's possible to do this ( https://auth0.github.io/nextjs-auth0/modules/handlers_login.html#getloginstate )
// from the documentation
const getLoginState = (req, loginOptions) => {
return { basket_id: getBasketId(req) };
};
From that doc - it looks like basket_id is the custom property to be saved against the user "before they visit the Identity Provider to login".
This sounds, to me, that basked_id will be saved somewhere against the users metadata once they've logged in. Is the documentation misleading, or am I misunderstanding?
How can I set the role during (or even slightly after) signup?
I managed to achieve what I wanted with the following Auth0 "rule":
function (user, context, callback) {
const count = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
if (count > 1) {
return callback(null, user, context);
}
const ManagementClient = require('auth0#2.27.0').ManagementClient;
const management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
let roles = [context.request.query.role];
const params = { id : user.user_id};
const data = { "roles" : roles};
management.users.assignRoles(params, data, function (err, user) {
if (err) {
// Handle error.
console.log(err);
}
callback(null, user, context);
});
}
Notice that the role is being read in from context.request.query.role. This pulls the query param role key off the login URL which more-or-less works how I wanted it to.
Then forward the role along from the auth in the backend:
const getLoginState = (req, loginOptions) => {
const { role } = req.query;
loginOptions.authorizationParams.role = role;
return { role: role };
};
export default handleAuth({
async login(req, res) {
try {
await handleLogin(req, res, { getLoginState });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
}
});
Notice the loginOptions.authorizationParams.role = role;
So the login link can be set to: /api/auth/login?role=somerole and the rule will pick up the role and set it in the metadata part of the users info.
However: I wasn't able to get this to actually properly set the role on the user but it's enough for me, as it appears in the session.

How to send properly a message in twilio conversation with react?

I have a nextjs project and I want to replace the twilio programmable chat with twilio conversations.
I did the following steps:
I did one API which creates or gets (if it is already created) the conversation and returns to the client the conversation unique name and the token
Once I have the conversation unique name and the token I want to send client side one message.
To do so I did the following function:
import { Client, State } from '#twilio/conversations';
import toast from 'react-hot-toast';
const sendMessageToConversation = async (
token: string,
room: string,
message: string
) => {
const client = new Client(token);
client.on('stateChanged', async (state: State) => {
if (state === 'initialized') {
try {
const conversation = await client.getConversationByUniqueName(room);
await conversation.join();
if (message && String(message).trim()) {
await conversation.sendMessage(message);
}
} catch {
toast.error('Unable to create conversation, please reload this page');
}
}
});
};
the problem seems to be const conversation = await client.getConversationByUniqueName(room); which gives the following error:
What do you think I did wrong?
Also is it a better idea to build an API to send messages in this way? I would avoid this because of the possible overhead of the server
UPDATE
I tried to send a message through API. It works and it returns what I expect. For more detail I will put also the code I have on backend side which generates the tokens and the conversations.
I generate tokens for client side with:
import Twilio from 'twilio';
import { config } from '../config';
const client = require('twilio')(
config.TWILIO_ACCOUNT_SID,
config.TIWLIO_AUTH_TOKEN
);
const AccessToken = Twilio.jwt.AccessToken;
const ChatGrant = AccessToken.ChatGrant;
const SyncGrant = AccessToken.SyncGrant;
export const tokenGenerator = (identity: string) => {
const token = new AccessToken(
config.TWILIO_ACCOUNT_SID,
config.TWILIO_API_KEY,
config.TWILIO_API_SECRET
);
token.identity = identity || 'unknown';
if (config.TWILIO_CHAT_SERVICE_SID) {
const chatGrant = new ChatGrant({
serviceSid: config.TWILIO_CHAT_SERVICE_SID,
pushCredentialSid: config.TWILIO_FCM_CREDENTIAL_SID,
});
token.addGrant(chatGrant);
}
if (config.TWILIO_SYNC_SERVICE_SID) {
const syncGrant = new SyncGrant({
serviceSid: config.TWILIO_SYNC_SERVICE_SID || 'default',
});
token.addGrant(syncGrant);
}
return {
identity: token.identity,
token: token.toJwt(),
};
};
I create conversations with:
const client = require('twilio')(
config.TWILIO_ACCOUNT_SID,
config.TIWLIO_AUTH_TOKEN
);
export const createTwilioConversation = async (
partecipantsProfiles: Partial<User>[],
identity: string
) => {
const friendlyName: string = partecipantsProfiles
.map((el) => `${el.first_name} ${el.last_name}`)
.join(' - ');
const conversation = (await client.conversations.conversations.create({
friendlyName,
uniqueName: uuidv4(),
})) as TwilioConversationResponse;
await client.conversations
.conversations(conversation.sid)
.participants.create({ identity });
return conversation;
};
The flow I do in order to send messages is:
If I want to send a message I create a conversation by calling an API, executes the functions above and returns the room unique name and the token. I also store into my DB the room unique name, participants and other infos. I do this only for the first message. If a user has already chatted with another user, then I don't create a conversation anymore, but I return the unique name stored + token generated and I get/send the message client side
I have also tried to send a message through API and it works. I can't figure out why I still can't get the conversation by unique name client side. Maybe I should generate the token differently?
This is the method that sends messages server side:
export const sendMessage = async (
conversationSid: string,
author: string,
body: string
) => {
return await client.conversations
.conversations(conversationSid)
.messages.create({ author, body });
};
Your issue is that you are not using the default Conversations service, but you are not scoping your API calls on the server side to the service you need.
So in pages/api/utils/conversations you need to add .services(config.TWILIO_CHAT_SERVICE_SID) into all your API calls. See below:
export const createTwilioConversation = async (
chatTo: string,
myIdentity: string
) => {
const uniqueName = uuidv4();
const conversation = (await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations.create({
friendlyName: `Chat created by ${myIdentity}`,
uniqueName,
})) as TwilioConversationResponse;
await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversation.sid)
.participants.create({ identity: chatTo });
await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversation.sid)
.participants.create({ identity: myIdentity });
return conversation;
};
export const sendMessage = async (
conversationSid: string,
author: string,
body: string
) => {
return await client.conversations
.services(config.TWILIO_CHAT_SERVICE_SID)
.conversations(conversationSid)
.messages.create({ author, body });
};
Once you've done that, you need to one other thing. Because you add your participant to the conversation using the REST API, you don't need to join the conversation in the front-end. So you can remove the line
await conversation.join();
from src/twilio/index.ts.
One last thing, you can get better error messages in the front-end if you log out error.body rather than just error or error.message.
try {
const conversation = await client.getConversationByUniqueName(room);
if (message && String(message).trim()) {
await conversation.sendMessage(message);
}
} catch (error) {
console.log("error", error);
console.log("error body", error.body);
toast.error("Unable to create conversation, please reload this page");
}
Twilio have an official blog for Build a Chat App with Twilio Programmable Chat and React.js, please check it out once,
Here is the link - https://www.twilio.com/blog/build-a-chat-app-with-twilio-programmable-chat-and-react

How to Connect Firebase Auth with Google One Tap Login

I have created a web app with Firebase and React.js and implemented sign-in with Google. I then tried to implement GoogleOneTapSignin and the one-tap-sign-in UI is working successfully because I used the react-google-one-tap-login npm package.
If may react app I have a function that listens for AuthStateChange and then either registers the user if they are new or sign in them if they are already a member and also updates the state if they logged. out.
Now that I have implemented google-one-tap-login, I was expecting the onAuthSTaetChanged function to be triggered if a user signs in using the google-one-tap-login but it is not the case.
Below is the part of my App.js code that handles the user auth.
const classes = useStyles();
const dispatch = useDispatch();
const alert = useSelector(state => state.notification.alert);
// Handling Google-one-tap-signin
useGoogleOneTapLogin({
onError: error => console.log(error),
onSuccess: response => {
console.log(response);
const credential = provider.credential(response);
auth.signInWithCredential(credential).then(result => {
const {
user
} = result;
console.log(user);
});
},
googleAccountConfigs: {
client_id: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
});
//Handling firebase authentification
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
// If there is a user create the user profile and update useState
if (user) {
// createUserProfile function creates the user profile in firestore if they are new
const userRef = await createUserProfileDocument(user);
userRef.onSnapshot(snapshot => {
const doc = snapshot.data();
dispatch(
setUser({
id: snapshot.id,
...doc
})
);
});
} else {
dispatch(setUser(null));
}
});
return () => {
unsubscribe();
};
}, [dispatch]);
I tried to implement the solution suggested by the 2nd answer in this StackOverflow question but I get the error below on the console. when I use google-one-tap-sign-in. Remember I am not using the FirebaseUi library. So far my application only uses the sign in with Google
t {
code: "auth/argument-error",
message: "credential failed: must provide the ID token and/or the access token.",
a: null
}
a: null
code: "auth/argument-error"
message: "credential failed: must provide the ID token and/or the access token."
The ID token required by Firebase's signInWithCredential function exists within the credential property of the response object. Here is a sample function below using Firebase V8.
// firebase V8
function handleCredentialResponse(response) {
if (response) {
const cred = auth.GoogleAuthProvider.credential(response.credential);
// Sign in with credential from the Google user.
return auth().signInWithCredential(cred);
}
}
Firebase v9
// firebase V9
import { getAuth, GoogleAuthProvider, signInWithCredential } from "firebase/auth";
const auth = getAuth();
function handleCredentialResponse(response) {
if (response) {
const cred = GoogleAuthProvider.credential(response.credential)
// Sign in with credential from the Google user.
return signInWithCredential(auth, cred);
}
}
The response param is a credential response returned from the Google one-tap function callback.
google?.accounts.id.initialize({
client_id: your-google-app-client-id.apps.googleusercontent.com,
callback: handleCredentialResponse,
});
google?.accounts.id.prompt((notification) => {
console.log(notification);
});

Invalid Credentials Error when tried to access a public data from a dynamo db table

I am using an aws-sdk in create-react-app to fetch dynamo DB data as a Guest user. In the Identity Pool, I have an unauthorized role that has limited access to a few tables that is public. When I tried to access data with the code below it shows Invalid credentials. I am new to aws, dynamo DB, I went through the documentation and tried out things. It's not worked as it is a bit different case.Please guide me the right approach to do this.
useEffect(() => {
AWS.config.update({
region: process.env.REACT_APP_REGION,
credentials: new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.REACT_APP_USER_POOL_ID
})
});
const docClient = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: "test_data",
Key:{ "id":"Test_2020_11_6_18"},
};
docClient.get(params, function(err, data) {
if (err) {
console.log(err);
} else {
console.log("Data"+ data)
}
});
}, [])
I will assume you have deployed the cognito federated identity and set
the role policy correctly.
I am used to with this approach to work with federated identity :
AWS.config.update({
region: process.env.REACT_APP_REGION
})
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
'IdentityPoolId': process.env.REACT_APP_USER_POOL_ID
});
const gp = AWS.config.credentials.getPromise();
gp.then(() => {
console.log(AWS.config.credentials.identityId)
const docClient = new AWS.DynamoDB.DocumentClient();
const params = {
TableName: "test_data",
Key: { "id": "Test_2020_11_6_18" },
};
docClient.get(params).promise().then(data => {
console.log("Data" + data)
}).catch(err => {
console.log(err);
})
}).catch((err) => {
console.log(err)
})

Resources