Good day! I’m trying to implement a Passwordless login using auth0 node package. Basically I’m trying to send magic link through email without getting the email value in the browser, so I’m getting the email from an API.
Note: The email that has been pulled from other API is already registered in Auth0
The problem was when I receive the link in my Inbox and click it, I’m getting the Opt-in that I should allow the app to access the profile, which is not the path that I’m expected to see. So here’s my code:
const AuthenticationClient = require('auth0').AuthenticationClient
app.get('/sendmagiclink', function(req, res) {
let auth0 = new AuthenticationClient({
domain: [Auth0 Domain],
clientId: [Auth0 Client ID],
clientSecret: [Auth0 Client Secret]
})
var data = {
email: 'myemail#gmail.com',
send: 'link',
authParams: {
connection: [My Connection]
} // Optional auth params.
};
auth0.passwordless.sendEmail(data, function (err) {
if (err) {
// Handle error.
}
});
})
Also, another problem with my code is the connection name which automatically sets to email rather than the custom connection name I created.
Your thoughts would be greatly appreciated.
I assume you are getting the Consent screen here. Are you using a localhost URL? In that case it's not possible to skip it, but it won't appear when your app is in production, or if you set up a domain in /etc/hosts file. More info here: https://auth0.com/docs/api-auth/user-consent#skipping-consent-for-first-party-clients
The name of the passwordless email connection is usually email.
Related
I am new to Auth0 and I am working on integrating the Auth0 with my NodeJS app. I have setup Auth0 with my NodeJS app and also I came to know how to create API in Auth0 and integrate with my App.
Now my doubt is whenever I add a new user under user management in Auth0, a verification email is sent to the user but instead of the verification email I want the user to get password change email is this possible to achieve ? When I cross checked most of the docs saying use management API or Authentication API or implements rules to achieve it but I am not sure how to implement it.
Since I am new to Auth0 is there a way for me to find step by step process to achieve it.
I have been working on a similar use case where I create the user using the Auth0 Management Client, then request a reset password email using the Auth0 Authentication Client. I have been following the "Send an Auth0 change password email using a customized email template" workflow described here. However, it lacks an implementation example:
import {
AuthenticationClient,
ManagementClient
} from "auth0";
const mc = new ManagementClient({
domain: process.env.AUTH0_DOMAIN,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
});
const ac = new AuthenticationClient({
domain: process.env.AUTH0_DOMAIN,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
});
(async function() {
await mc.createUser({
connection: "Username-Password-Authentication",
email: "newuser#example.com",
email_verified: false, // this is implicitly set to true when the user follows the reset password link in the email they receive, but should be false for now
name: "New User",
password: "A random placeholder conforming to your policy", // e.g. "npm install generate-password"
});
await ac.requestChangePasswordEmail({
email: "newuser#example.com",
connection: "Username-Password-Authentication",
});
})()
Finally, should you require the email to be an invitation email, you can customise the Reset Password email template and Reset Password page in the Auth0 Management Console. Using the liquid syntax, you can check for user.email_verified == false to conditionally append an isInvitation=true parameter to the {{ url }} of the email and test for this parameter in the reset password page logic. In my case I use window.location.href.includes("isInvitation=true"). Check out:
https://auth0.com/docs/auth0-email-services/customize-email-templates
https://auth0.com/docs/auth0-email-services/customize-email-templates/use-liquid-syntax-in-email-templates
https://auth0.com/docs/universal-login/customize-password-reset-page
Goal: I am working on a side project that uses React for frontend and Express.js for backend. My goal is to send acknowledgement emails to logged in users via nodemailer using express api.
Progress:
I have set up HTTPS for my react app that lives in Amazon s3 via Cloudfront and route53. I did this by attaching the domain name(https://www.example.com) to the cloudfront distribution via Route53. I used AWS ACM to create the cert.
My backend code lives in an EC2 instance, I have set up HTTPS for the backend api api.example.com. I used certbot to configure lets encrypt cert and pointed nginx server name to api.example.com to configure HTTPS.
I have setup a backend api to notify logged in users. I send emails using nodemailer.
const nodemailer = require("nodemailer");
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.USERNAME,
pass: process.env.PWD
},
});
exports.emailAck = function (req, res) {
const mailOptions = {
from: 'Testing email',
to: req.body.email,
subject: "Thank you for submitting the request for : " + req.body.id,
text: "TBD",
};
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
res.json({status: "error", message: error});
}
else {
res.json({ status: "success", message: "Email sent" });
}
});
};
Problem:
My emails are being sent only when I turn on allow less secure apps in gmail.. Even though my app is secured, I am not sure why this is happening
When I try to store my transporter auth credentials in .env file to protect the secrets, I see a Error: Missing credentials for "PLAIN" error but if I try to add plaintext in the given file below, it works.
I use https://www.npmjs.com/package/react-google-login in the react side to setup login functionality.
I am wondering if I should restrict my backend api in EC2 to just allow access to the react app so it wouldn't be considered less secure application...
I would appreciate any direction with the above two issues,If there is a better solution other than node mailer, I am open to trying it.
Thanks in advance. I can clarify any questions in the edits if anything is not clear, so please let me know.
For issue number 2, if you are using dotenv package for environment variables,you should load the config like this in your js file
require('dotenv').config()
I'm creating a Twitter bot and I'm implementing a method that sends me a email if there is an error. As I'm already using the google API to access Google Drive (have no problem here), I decided to use the service account to send the email (Google console says it could be used that way)
The method I've come up to send the email so far is:
var config = require('./config/mail');
var google = require('./config/google');
var nodemailer = require('nodemailer');
var send = function (args) {
let transporter = nodemailer.createTransport({
'service': 'gmail',
'auth': {
'type': 'OAuth2',
'user': google.client_email,
'serviceClient': google.client_id,
'privateKey': google.private_key
}
});
transporter.on('token', token => console.log(token));
let message = {
'from': `"${config.serverFromName}" <${config.serverFromMail}>`,
'to': args.to,
'subject': args.subject,
'text': args.text,
'html': `<p>${args.text}</p>`
};
transporter.sendMail(message, (err, info) => {
if (err) {
console.log('Mail couldn\'t be sent because: ' + err);
} else {
console.log('Mail sent');
}
});
};
The config/google file contains the data that Google generates for you when you create a service account. config.serverFromName and config.serverFromMail are the name and email of the sender (not the same as the service account id). args contains the recipent email and the content
When I test the send method, I got the following message in my console:
Mail couldn't be sent because: Error: Invalid login: 535-5.7.8 Username and Password not accepted. Learn more at
535 5.7.8 https://support.google.com/mail/?p=BadCredentials z123sm543690vkd.10 - gsmtp
I know the token is being created correctly because the listener I created is printing it:
{ user: 'name#project.iam.gserviceaccount.com',
accessToken: 'ya29.ElmIBLxzfU_kkuZeyISeuRBeljmAe7HNTlwuG4K12ysUNo46s-eJ8NkMYHQqD_JrqTlH3yheNc2Aopu9B5vw-ivEqvPR4sTDpWBOg3xUU_4XiJEBLno8FHsg',
expires: 1500151434603 }
Searching on the Internet I found that it may be a problem with the OAuth scope. However, all the info that talks about it refers to using Client IDs, not service accounts. I don't find that option in the Google developer console, either.
Any ideas of what I'm doing wrong?
Bottom Line: The specific way Google describes a service account is INCOMPATIBLE with nodemailer. BUT there is a way!
I have just spent countless hours myself up over this same issue! I have come to the conclusion, Google's Admin Console has removed half this capability indirectly. The console does not provide a way to authorize (a user accepting the consent screen) the desired scope the very first time with a service account.
First up, follow the Node.JS Quickstart instructions for Google Drive API to authorize a scope and receive a refresh token.
Go to console.developers.google.com, build a OAuth2.0 Client Id, and download the client_secret.json file.
Create a separate temporary module folder and use NPM to download google api modules
npm install googleapis
npm install google-auth-library
Create a quickstart.js file
Place your client_secret.json file next to quickstart.js
Line 7 in the quickstart.js is the array to define the scopes you intend to allow the application to access. Modify it as you see necessary. It is highly recommended to only provision access for what is intended. See Gmail API Scopes.
RUN node quickstart.js
Open the URL in a browser, authenticate, and copy the code from the browser back into the terminal window. This will download a nodejs-gmail-quickstart.json file which the location will be provided in stdout.
This is the part you are unable to accomplish for a Service Account. This action authorizes the scopes provided in the SCOPES array to the downloaded access_token & refresh token.
NOTE: access_token's have a lifespan of 1 hour. refresh_token's are immortal.
Now you have an authorized refresh_token!
Next is setting up your auth object with 3LO in Nodemailer. I would look more at the bottom examples because not all values are required. My auth looks like this:
const mailbot = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 587, // TLS (google requires this port for TLS)
secure: false, // Not SSL
requireTLS: true, // Uses STARTTLS command (nodemailer-ism)
auth: {
// **HIGHLY RECOMMEND** ALL values be
// read in from a file not placed directly in code.
// Make sure that file is locked down to only the server daemon
type : 'OAuth2',
user : config.client_email,
scope : "https://www.googleapis.com/auth/gmail.send",
clientId : config.client_id,
clientSecret: secret,
refreshToken: activeToken.refresh_token
// AT RUNTIME, it looks like this:
//type : 'OAuth2',
//user : 'user#gmail.com', // actual user being impersonated
//scope : "", //Optional, but recommend to define for the action intended
//clientId : '888888888998-9xx9x99xx9x99xx9xxxx9xx9xx9x88x8xxx.apps.googleusercontent.com',
//clientSecret: 'XxxxxXXxX0xxxxxxxx0XXxX0',
//refreshToken: '1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx'
}
});
TIP: Gmail will rewrite the FROM field from any email sent with the authorized user account (user impersonated). If you want to customize this slightly, use the syntax { FROM: '"Display NAME" <user email>' } and it will not overwrite your display name choice since the email matches.
NOTE: nodemailer will make a token request out to https://accounts.google.com/o/oauth2/token with the refresh token to automatically obtain an access_token.
Unfortunately, nodemailer lacks the functionality to save a received token out to a file directly but instead just uses this.emit(). If the server stays active it will not be an issue but as mine is only bursting, it will always incur a delay as a new access_token will be requested every time.
[SECURITY] Hopefully this works for you! It is disappointing to loose the private key encryption a service account with 2LO would bring but at least this Client ID way is very hard to spoof. I was concerned about security but reading more I am okay with this implementation. See Google Identity Platform (Nodemailer uses the HTTP/REST details) and given
[1] Google's OAuth 2.0 endpoint is at
https://accounts.google.com/o/oauth2/v2/auth. This endpoint is
accessible only over HTTPS. Plain HTTP connections are refused.
[5] After the web server receives the authorization code, it can exchange
the authorization code for an access token.
you are using TLS to connect initially for an authorization code, then matching it with your client ID data, and a refresh_token (you must go through the hassle we did above) then you can receive an access_token to actually interact with Google APIs.
As long as you increase your security posture with keeping the OAuth2.0 Client ID (highly random username), secret, and refresh token as separate, secure, and hidden as much as possible, you should be able to sleep soundly. GOOD LUCK!
After visiting the OAuth 2.0 Playground and experimenting with all possible variations of gmail-related sub-scopes, even selecting them altogether...
https://www.googleapis.com/auth/gmail.labels
https://www.googleapis.com/auth/gmail.send
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.compose
https://www.googleapis.com/auth/gmail.insert
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/gmail.metadata
https://www.googleapis.com/auth/gmail.settings.basic
https://www.googleapis.com/auth/gmail.settings.sharing
...the error message described in the OP title still persist:
Error: Invalid login: 535-5.7.8 Username and Password not accepted
It seems that NodeMailer is not capable of connecting via the scopes mentioned above. In fact, it explicitly mentions in the "Troubleshooting" section of its OAuth2 SMTP transport docs
The correct OAuth2 scope for Gmail SMTP is https://mail.google.com/, make sure your client has this scope set when requesting permissions for an user
Although this gives access to more than just sending emails, it works!
The only alternative to reach a more fine grained scope solution seems to be to resort to google's own Gmail API, where you can pass scopes when generating the OAuth2 client (which should of course at least include the scopes granted at the time the OAuth consent screen was shown):
oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
})
I was able to get service accounts working with Google & nodemailer:
these were the steps:
Log in to console.- https://console.cloud.google.com/
Create a service account under the project.
Click on the new service account, go to permissions and add a member. You will use this member's email address when sending the request.
Create keys for the service account. - keys -> add key. https://console.cloud.google.com/iam-admin/serviceaccounts
Download your key file. You will get something like service-account-name-accountid.json. It will have all the information you need to get the code below running.
Delegate authority to your service account https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority. Addhttps://mail.google.com/ as the scope.
Write some code like below:
const nodemailer = require('nodemailer');
const json = require('./service-account-name-accountid.json');
const sendEmail = async (email, subject, text) => {
try {
const transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
type: 'OAuth2',
user: email, //your permissioned service account member e-mail address
serviceClient: json.client_id,
privateKey: json.private_key
}
});
await transporter.verify();
await transporter.sendMail({
from: json.service_email,
to: email, //you can change this to any other e-mail address and it should work!
subject,
text
});
console.log('success!');
return {
status : 200
}
} catch (error) {
console.log(error);
return {
status : 500,
error
}
}
}
sendEmail('your_permissioned_service_account_email_address#some_place.com, 'testing 123', 'woohoo!');
I'm setting up Auth0 with SSO enabled for multiple web sites.
I have it working perfectly for username/password connections and for google connections where the user is only logged into a single google account.
But... I have two google accounts logged in on my browser. When I go to site 1 and use the google connection, it prompts me to select a google account to log in with. That's correct.
But when I go to site 2, and attempt to stay logged in using sso data from Auth0, it still prompts me to select a google account again. Which I would not expect.
Here's my sign in code:
auth0.getSSOData(function (err, ssoData) {
if (!err && ssoData.sso) {
auth0.signin({
connection: ssoData.lastUsedConnection.name,
scope: 'openid email email_verified name app_metadata',
state: 'http://localhost:21763/#/',
authParams: {
prompt: 'none'
}
});
}
});
I added prompt: 'none' having read this older post on Auth0 community, but to no avail. https://auth0.com/forum/t/sso-login-with-google-connection-must-select-account/4983
Has anyone else done this? I feel like it must be possible.
Thanks!
I started experimenting with the possible parameters into the signin method call and stumbled across the answer...
I tried a few things, but this seems to work perfectly, moving the "prompt: 'none'" up out of the authParams to be a direct option to signin method call...
auth0.getSSOData(function (err, ssoData) {
if (!err && ssoData.sso) {
auth0.signin({
connection: ssoData.lastUsedConnection.name,
scope: 'openid email email_verified name app_metadata',
state: 'http://localhost:21763/#/',
prompt: 'none'
});
}
});
I'm trying to create a rest api for a service I'm working on.
The service has two parts to it - the website and the mobile client. Basically, the mobile device keeps its location up to date via the api, the website displays the data via the api.
Seeing as my application only targets Android, I'm hoping to use 'Sign in with Google' as the authentication mechanism for both the mobile and website clients.
The API is using Node.js and Express.js. I'm running into trouble when generating new user accounts though. Seeing as I don't want to trust data from the client, my expected sign up process was something like this:
Through the website:
User visits website, hits 'Sign up with Google'.
User accepts the app request to see their Google details.
Website gets a google auth token back, which it sends to the api.
API contacts google with that auth token to get the user details.
API creates a new user in the database along with my own form of access token.
API returns my own access token to the client for future request signing.
Through the Android app:
User downloads the app, runs and hits 'Sign up with Google'.
User accepts authorisation step presented by google.
App gets a token, which it sends to the API.
API contacts google with that auth token to get the user details.
API realises the user exists and registers this new device with that user.
API returns my own access token to the app for future request signing.
I'm running into a lot of trouble here as soon as the token gets to the server though. Every time I use the token generated, I just get an error 'Invalid Credentials'.
Initially I started to use Passport.js, but what I found was this. In the documentation it states setup happens like so:
passport.use(new GoogleStrategy({
returnURL: 'http://www.example.com/auth/google/return',
realm: 'http://www.example.com/'
},
function(identifier, profile, done) {
User.findOrCreate({ openId: identifier }, function(err, user) {
done(err, user);
});
}));
But when I log the contents of 'identifier' it is actually something like
https://www.google.com/accounts/o8/id?id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I assume the ID is something unique to me but I can't seem to discover exactly what it is. Furthermore I don't know if it is time-limited or will last forever. As a final problem, I don't even know if I can get that same value when signing up via Android because I don't know where the value comes from. It's not the kind of API access token that I was expecting. When I output the contents of profile, it's just my name and email address - nothing that I can use for contacting the Google API with to verify the user.
The above solution I don't like anyway because it means the server hosting the client site has to make an api request in order to pass the id to the api. Or it sends the id details to the client so that it can pass them on to the api server. Or, the website server puts it into the api database, which is also a bad solution.
So next I figured I would use the javascript library from the Google sign in docs. I have something like this:
Website Client:
<script type="text/javascript">
function createUser(token)
{
$.ajax({
url:"http://api.example.com/user",
dataType: 'jsonp',
data: 'token='+token,
success:function(json){
alert("Success: "+json);
},
error:function(jqXHR, textStatus, errorThrown){
alert("Error "+textStatus+" "+errorThrown);
}
});
}
function signinCallback(authResult)
{
if(authResult['access_token'])
{
document.getElementById('signinButton').setAttribute('style', 'display: none');
alert('RES: '+JSON.stringify(authResult));
createUser(authResult['access_token']);
}
else if(authResult['error'])
{
alert('There was an error: ' + authResult['error']);
}
}
</script>
Node API user handling function:
function(req, res)
{
var callback = req.query.callback;
if(callback == null)
{
res.send("{valid:false}");
}
else
{
var token = req.query.token;
if(token == null)
{
res.send("{valid:false}");
}
else
{
var oauth2Client = new OAuth2Client('xxxxxx', 'xxxxxx', '');
oauth2Client.credentials = {
access_token: token
};
googleapis
.discover('plus', 'v1')
.execute(function(err, client){
if(client == null)
{
console.log("Client is null");
}
else
{
var request1 = client.plus.people.get({ userId: 'me' })
.withApiKey('xxxxxx');
request1.execute(function(err, result){
console.log("Result: " + (err ? err.message : result.displayName));
});
}
});
res.set('Content-Type', 'text/javascript');
res.send(callback+"({ret:'User Test'});");
}
}
}
This works fine on the client side - I see the alert box with my access token and some other details. The trouble is that when I call the google api functions on my api server for getting the user details, I get 'Invalid Credentials' returned. I assume this is because I generated the access token in javascript for a website and I'm using it from somewhere else.
So, that pretty much leaves me out of ideas. Is there an easy way to achieve this that I'm missing? I just want to be able to generate a token from a website and from an Android app that I can use on the server for validating the user's details. The generated token doesn't even need to work on the website or the Android app, just from the api server. The API server can't do the process of directing the user to Google for authorisation though because the user doesn't directly interact with it.