I have set up firebase authentication and am trying to send a welcome email
What I do not understand is why the authenticated user does not have an email field and only has this in the provider data. All the samples use something similar to:
exports.sendWelcomeEmail = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
const email = user.email; // The email of the user.
const displayName = user.displayName; // The display name of the user.
console.log("New User created: " + JSON.stringify(user));
console.log('email:', email);
However, my authenticated user does not seem to me to have an email
My log shows
email: undefined
I am using google authentication from IOS with GIDSignInButton
My console log has the following
{
"displayName": "Ryan H",
"metadata": {
"createdAt": "2017-06-19T10:06:21.000Z",
"lastSignedInAt": "2017-06-19T10:06:21.000Z"
},
"photoURL": "https://lh6.googleusercontent.com/-MXne-lIR8e8/AAAAAAAAAAI/AAAAAAAAbeQ/Z1OvxasY/s96-c/photo.jpg",
"providerData": [
{
"displayName": "Ryan H",
"email": "ryanh#gmail.com",
"photoURL": "https://lh6.googleusercontent.com/-MXne-lIR8e8/AAAAAAAAAAI/AAAAAAAAbeQ/Z1asY/s96-c/photo.jpg",
"providerId": "google.com",
"uid": "1077081708"
}
],
"uid": "WjdlLc3QNvrmkj0yOuqo2"
}
As you can see there is no email except in the provider data.
Has there been a change in the model?
Should I always try to get my email address from the provider data?
Why are all the same code and examples I find using user.email ?
I currently have google and facebook enabled as "sign in methods".
Perhaps if I also enabled email/password I would have access.
I can confirm that in my IOS app the User after sign in does not have email either.
According to https://firebase.google.com/docs/auth/users
The first time a user signs up to your app, the user's profile data is populated using the available information:
If the user signed up with an email address and password, only the primary email address property is populated
If the user signed up with a federated identity provider, such as Google or Facebook, the account information made available by the provider is used to populate the Firebase User's profile
This is a workaround to my issue not really an answer:
I extract an email from the first provider I find, which in my case is always
Google
exports.sendWelcomeEmail = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
var email = user.email; // The email of the user.
if (email == undefined) {
for (var provider of user.providerData) {
if (provider.email) {
email = provider.email;
break;
}
}
}
const displayName = user.displayName; // The display name of the user.
// [END eventAttributes]
console.log("New User created: " + JSON.stringify(user));
console.log('email:', email);
console.log('displayName:', displayName);
sendWelcomeEmail(email,displayName)
return;
});`
Related
I'm building a simple commandline application in Java, that logs into my email box (IMAP) and downloads all attachments. I used basic authentication, but Microsoft is in the process of disabling that so I try to convert my application to use OAuth instead.
After reading on the different OAuth flows, it seems that for my simple standalone commandline application, where there is no problem to simply hardcode a password, the Resource Owner Password Credentials Grand (as described here) would be the best (or a good) choice. I further based myself on the instructions from this source where it is described how to enable OAuth using recent versions of Javamail.
Putting it all together seems a bit harder, and I keep getting AUTHENTICATE Failed errors.
So, what did I try? I first retrieve my authorization token as follows:
public String getAuthToken() {
try {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost loginPost = new HttpPost("https://login.microsoftonline.com/organizations/oauth2/v2.0/token");
String clientId = "some client UUID";
String scopes = "email openid IMAP.AccessAsUser.All offline_access";
String client_secret = "My client secret, not base64 encoded";
String username = "my emailadress";
String password = "my password, not base64 encoded";
String encodedBody = "client_id=" + clientId
+ "&scope=" + scopes
+ "&client_secret=" + client_secret
+ "&username=" + username
+ "&password=" + password
+ "&grant_type=password";
loginPost.setEntity(new StringEntity(encodedBody, ContentType.APPLICATION_FORM_URLENCODED));
loginPost.addHeader(new BasicHeader("cache-control", "no-cache"));
CloseableHttpResponse loginResponse = client.execute(loginPost);
byte[] response = loginResponse.getEntity().getContent().readAllBytes();
ObjectMapper objectMapper = new ObjectMapper();
JavaType type = objectMapper.constructType(objectMapper.getTypeFactory()
.constructParametricType(Map.class, String.class, String.class));
Map<String, String> parsed = new ObjectMapper().readValue(response, type);
return parsed.get("access_token");
} catch (Exception e) {
e.printStackTrace();
return null;
}
The response from the oauth service is actually a json-object which contains following fields:
Obviously the tokens are much longer, but are not shared here. The access_token itself is in the form of three base64 encoded strings seperated by a . The first, when decoded contains
{
"typ": "JWT",
"nonce": "Vobb8bI7E...",
"alg": "RS256",
"x5t": "2ZQpJ3Up...",
"kid": "2ZQpJ3Up..."
}
the second part is a larger object, containing following fields (redacted as well):
{
"aud": "someuuid",
"iss": "https://sts.windows.net/someuuid/",
"iat": 1658397625,
"nbf": 1658397625,
"exp": 1658402597,
"acct": 0,
"acr": "1",
"aio": "ASQ....",
"amr": [
"pwd"
],
"app_displayname": "myapp",
"appid": "some uuid",
"appidacr": "1",
"family_name": "My Last Name",
"given_name": "My First Name",
"idtyp": "user",
"ipaddr": "some.ip.address.here",
"name": "My Full name",
"oid": "someuuid",
"platf": "14",
"puid": "10032...",
"rh": "0.AToA....",
"scp": "email IMAP.AccessAsUser.All openid profile",
"sub": "enaKK...",
"tenant_region_scope": "EU",
"tid": "someuuid",
"unique_name": "my email",
"upn": "my email",
"uti": "1cc...",
"ver": "1.0",
"wids": [
"some uuid",
"some uuid"
],
"xms_st": {
"sub": "02n7h..."
},
"xms_tcdt": 1571393936
}
The last part is just binary data. I currenly simply pass on the entire access_token as I receive it to JavaMail as follows:
String accesstoken = new OauthTokenFetcher().getAuthToken();
imapReader = new ImapMailBoxReader(
"outlook.office365.com",
"my email",
accesstoken);
LocalDate startDate = LocalDate.of(2022,4,1);
LocalDate endDate = LocalDate.of(2022,7,1);
imapReader.processOnMessages("Inbox", startDate, endDate,this::processMessage);
with ImapMailBoxReader as follows:
public class ImapMailBoxReader {
private String host;
private String username;
private String password;
public ImapMailBoxReader(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public void processOnMessages(String folder, LocalDate since, LocalDate until, Consumer<Message> mailconsumer) {
try {
System.out.println("Password:" + password);
Properties prop = new Properties();
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true);
prop.put("mail.debug.auth", "true");
prop.put("mail.imap.sasl.enable", "true");
prop.put("mail.imap.sasl.mechanisms", "XOAUTH2");
prop.put("mail.imap.auth.login.disable", "true");
prop.put("mail.imap.auth.plain.disable", "true");
prop.put("mail.imap.ssl.enable", "true");
// Create the session
//Connect to the server
Session session = Session.getDefaultInstance(prop, null);
session.setDebug(true);
Store store = session.getStore("imap");
store.connect(host, username, password);
//open the inbox folder
Folder inbox = store.getFolder(folder);
inbox.open(Folder.READ_ONLY);
Message[] messages;
if (since != null) {
Date startDate = Date.from(since.atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm newerThan = new ReceivedDateTerm(ComparisonTerm.GE, startDate);
if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
SearchTerm both = new AndTerm(olderThan, newerThan);
messages = inbox.search(both);
} else {
messages = inbox.search(newerThan);
}
} else if (until != null) {
Date endDate = Date.from(until.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
SearchTerm olderThan = new ReceivedDateTerm(ComparisonTerm.LT, endDate);
messages = inbox.search(olderThan);
} else {
messages = inbox.getMessages();
}
for (Message m: messages) {
mailconsumer.accept(m);
}
inbox.close(false);
store.close();
} catch (Exception e) {
e.printStackTrace();
}
}
The above statement fails at the store.connect statement with AUTHENTICATE FAILED.
I probably pass on the token incorrectly? The JavaMail documentation above states that I should not Base64 encode the token, but I received it as such. Am I supposed to send only part of it? Which part then?
Any help would be appreciated.
With a tip from a co-worker without a stackoverflow account, I finally got it to work. The key was that the scopes I used for the OAuth token, are apparantly not allowed for an application. This bit of information is hidden at the bottom of this page.
Summarized, the solution is:
You must configure the IMAP.AccessAsApp permission, instead of IMAP.AccessAsUser.All . This permission can not be found in the same place as the AccessAsUser.All permission, but is hidden under "Office 365 Exchange Online" permissions.
Unlike what you would expect, you must use the https://outlook.office365.com/.default scope in the body payload for the access token request.
That did the trick. It is ridiculous how much difficulty I had to find that information in the documentation pages using search engines.
Please check if you have enabled Allow public client flows setting in azure app registrations. This is required for ROPC as description says.
Allow public client flows
This is under App Registrations -> Overview-> redirect URIs.
I had a similar situation as the OP. I had a Java (swing) standalone app that reads and processes specific messages from the inbox. The app has been running for a couple of years using basic auth. Recently (Early October), Microsoft disable basic auth. I had to scramble to figure out OAUTH. This post got me very close; HOWEVER, the final answer did not work for me.
Getting the access token was easy enough, but authentication ALWAYS failed. Obviously, the permissions/grants for my token were not correct.
My solution:
Configure Microsoft Graph permissions IMAP.AccessAsUser.All . (opposite of OP's solution)
Scope - "email openid https://outlook.office.com/IMAP.AccessAsUser.All"
Everything else pretty much exactly followed OP.
Hope this might help another lost soul.
I am completely new in Twilio and I am considering to build an app where users can make calls to phone numbers.
From Twilio side of things, I guess we need to have Twilio accounts for each user (where each user will have it's own Twilio phone number, required for the ability to make calls).
So I guess that we need to have regular user accounts in our DB and for each regular user, to store Twilio auth token (and other Twilio credentials if needed) as a user fields. When user needs to make a call, we just use the corresponding auth token.
The users themselves, have Twilio accounts and numbers, but they should not be aware of them. They are only aware of the app, where they login and make calls. I guess that admin should create their Twilio accounts in the Twilio console for them? I am OK with admin paying the bills for the calls, since users will be doing the calls on admin's request.
Is that how Twilio should work and how it should be connected to the regular user profiles, for the cases when we have fully customized UI?
you create your master twilio account. Then everytime a user creates an account on your app, you programmatically create a sub account for each of your users. Then when you buy phone numbers, you buy them through your master account credentials, and pass the phone number down to the sub account. pretty sure thats how it works. check this passport login i created for something like that
passport.use(
new GoogleStrategy({
clientID: keys.googleClientID,
clientSecret: keys.googleClientSecret,
callbackURL: '/auth/google/callback',
proxy: true
},
async (accessToken, refreshToken, profile, done) => {
const existingUser = await User.findOne({googleId: profile.id})
if (existingUser) {
done(null, existingUser)
} else {
const client = require('twilio')(accountSid, authToken); // get our master account credentials
const actServiceSid = await client.api.accounts // create sub account, this variable holds the return data
.create({friendlyName: profile.displayName }) // name it with profiles displayName ex. Christian Moosey
.then(account => { return {sid: account.sid, authToken: account.authToken}}) // return our new sub account auth credentials
const subClient = require('twilio')(actServiceSid.sid, actServiceSid.authToken) // these are our sub account credentials
const msgServiceSid = await subClient.messaging.services // subaccount credentails / whatever service we need
.create({
friendlyName: profile.displayName,
inboundRequestUrl: "http://a30-b81e-5e93-f40b-34f.ngrok.io/webhook-incoming-message"
}) // create messaging service name
.then(service => { return console.log(service), {sid: service.sid}}) // return the sid in an object
const convoServiceSid = await subClient.conversations.services // subaccount credentials / create conversations service
.create({friendlyName: profile.displayName}) // make it look like christian moreno
.then(service => { return {sid: service.sid}}); // return and object with a sid key to convoServiceSid variable
const keys = await subClient.newKeys.create({friendlyName: profile.displayName})
.then(new_key => { return { keySid: new_key.sid, keySec: new_key.secret}})
const twimlApp = await subClient.applications
.create({
voiceMethod: 'POST',
voiceUrl: 'https://-00-96fb.ngrok.io/voice',
friendlyName: 'Phone Me'
})
.then(application => { return {twimlApp: application.sid}});
const customer = await stripe.customers.create({
email: profile.emails[0].value,
}).then(customer => { return {customerId: customer.id}});
const user = await new User({
googleId: profile.id,
displayName: profile.displayName,
image: profile.photos[0].value,
email: profile.emails[0].value,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
msgServiceSid: msgServiceSid.sid,
convoServiceSid: convoServiceSid.sid,
subActServiceSid: actServiceSid.sid,
subActAuthToken: actServiceSid.authToken,
stripeCustomerId: customer.customerId,
twilKeySid: keys.keySid,
twilKeySec: keys.keySec,
twimlAppSid: twimlApp.twimlApp
}).save()
done(null, user);
}
}
)
)
I have a system comprised of an Angular SPA hosted in Azure and some Azure Functions for the APIs. In an administrative app, I created an application that allows admin users to create new user accounts including specifying a password. These new accounts are able to log into the line of business app that I created as well. There is a requirement where we need to allow the same people who created the account to reset a password. For some reason, the code that I wrote to set the password does not work. It seems odd that a user can create an account, including setting the password, but for some reason the same user can't set the password independent of creating the user account. FYI, there are no emails, these are user accounts, so giving the ability to request a password reset is not an option.
Here is the error:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Access to change password operation is denied.",
"innerError": {
"date": "2021-01-19T21:58:35",
"request-id": "a1bc5b50-83e9-47ae-97c7-bda4f524fa0e",
"client-request-id": "a1bc5b50-83e9-47ae-97c7-bda4f524fa0e"
}
}
}
Here is my code:
//this is the method that works
public async Task<Microsoft.Graph.User> CreateUserAsync(string givenName,
string surname, string displayName, string userPrincipalName, string issuer,
string signInType, string initialPassword, GraphServiceClient graphClient)
{
var user = new Microsoft.Graph.User {
AccountEnabled = true,
GivenName = givenName,
Surname = surname,
DisplayName = displayName,
Identities = new List<ObjectIdentity>() {
new ObjectIdentity {
Issuer = issuer,
IssuerAssignedId = userPrincipalName,
SignInType = signInType
}
},
PasswordProfile = new PasswordProfile {
ForceChangePasswordNextSignIn = false,
Password = initialPassword
}
};
return await graphClient.Users
.Request()
.AddAsync(user);
}
//This one does not work, returns: Access to change password operation is denied.
public async Task<Microsoft.Graph.User> SetPasswordAsync(
string userName, string currentPassword, string newPassword, GraphServiceClient graphClient)
{
await graphClient.Users[userName].ChangePassword(currentPassword, newPassword).Request().PostAsync();
return something here;
}
Seems this is a permission issue here. The function:graphClient.Users[].ChangePassword()is based on reset password rest API,as the official doc indicated, only delegated permission works here and permission :UserAuthenticationMethod.ReadWrite.All is needed.
After I granted this permission to my app:
it works perfectly for me:
Let me know if you have any further questions.
I am using firebase from google and I have some trouble with user authentication. After logging with facebook I obtain FirebaseUser in AuthStateListener, but how can I detect if this user is logged via facebook or differently?
UPDATE
As #Frank van Puffelen said FirebaseAuth.getInstance().getCurrentUser().getProviderId()
should return "facebook", but in my case it returns "firebase". Now I cannot figure out what's the reason of this behavior. When I got FacebookToken I do something like this:
AuthCredential credential = FacebookAuthProvider.getCredential(facebookToken.getToken());
mAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
}
}
});
And afterthat before onComplete() method is called, my AuthStateListener gets user which provider id is not "facebook" as it should be. Am I doing something wrong? I followed official google documentation
In version 3.x and later a single user can be signed in with multiple providers. So there is no longer the concept of a single provider ID. In fact when you call:
FirebaseAuth.getInstance().getCurrentUser().getProviderId()
It will always return firebase.
To detect if the user was signed in with Facebook, you will have to inspect the provider data:
for (UserInfo user: FirebaseAuth.getInstance().getCurrentUser().getProviderData()) {
if (user.getProviderId().equals("facebook.com")) {
System.out.println("User is signed in with Facebook");
}
}
In my app, I use Anonymous Firebase accounts. When I connect Firebase auth with a Facebook account or Google Account I am checking like the following:
for (UserInfo user: FirebaseAuth.getInstance().getCurrentUser().getProviderData()) {
if (user.getProviderId().equals("facebook.com")) {
//For linked facebook account
Log.d("xx_xx_provider_info", "User is signed in with Facebook");
} else if (user.getProviderId().equals("google.com")) {
//For linked Google account
Log.d("xx_xx_provider_info", "User is signed in with Google");
}
}
For me, the following solution is working.
First, get the firebase user object if you have'nt already:
FirebaseAuth mAuth = FirebaseAuth.getInstance();
FirebaseUser firebaseUser = mAuth.getCurrentUser();
Now use the following on the FirebaseUser object to get the sign in provider:
firebaseUser.getIdToken(false).getResult().getSignInProvider()
Sources:
https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseUser
https://firebase.google.com/docs/reference/android/com/google/firebase/auth/GetTokenResult.html
It will return password, google.com, facebook.com and twitter.com for email, google, facebook and twitter respectively.
Sharing for FirebaseAuth targeting version 6.x.x (Swift 5.0), year 2020:
I use Auth.auth().currentUser?.providerData.first?.providerID.
This will return password if logged in via email. And facebook.com if via Facebook.
There exist information in the responding Intent.
Refer to following snippet:
The responseCode is either "phone", "google.com", "facebook.com", or "twitter.com".
`import com.firebase.ui.auth.AuthUI;
import com.firebase.ui.auth.IdpResponse;
.....
#Override
protected void onActivityResult(final int requestCode, int resultCode, Intent
data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
progressBar.setVisibility(View.VISIBLE);
IdpResponse response = IdpResponse.fromResultIntent(data);
if (resultCode == RESULT_OK) {
String providerCode = response.getProviderType();
...
}
}
Most recent solution is:
As noted here
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function(authResult, redirectUrl) {
var providerId = authResult.additionalUserInfo.providerId;
//...
},
//..
}
and for display in page
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
user.getIdToken().then(function (idToken) {
$('#user').text(welcomeName + "(" + localStorage.getItem("firebaseProviderId")+ ")");
$('#logged-in').show();
}
}
});
I am creating a new PFUser to be a "sub user" of the currently logged in user.
For example Master is logged in and decides to create a Junior User.
The Junior user will have a different PFRole to the Master.
I can create a new User via
var newUser = PFUser()
newUser.email = "someemail#gmail.com"
newUser.username = "Demo Subuser"
newUser.password = "12341234"
newUser.signUpInBackgroundWithBlock { (newUser, error) -> Void in
println(error)
}
The user is created, but the problem is I am logged out of the master account and logged in as the Junior user.
How can I create a new PFUser without it logging into that account.
Ideally, I want to send an email to the new subuser stating, welcome here is your username and details.
Maybe I should be using CloudCode ?
Thanks
Create a Cloudcode function and upload it to Parse
Parse.Cloud.define("createNewUser", function(request, response) {
// extract passed in details
var username = request.params.username
var pw = request.params.password
var email = request.params.email
// cloud local calls
var user = new Parse.User();
user.set("username", username);
user.set("password", pw);
user.set("email", email);
user.signUp(null, {
success: function(user) {
//response.error("working");
// do other stuff here
// like set ACL
// create relationships
// and then save again!! using user.save
// you will need to use Parse.Cloud.useMasterKey();
},
error: function(user, error) {
//response.error("Sorry! " + error.message);
} });
});
Call the cloud code function from within your app via. Pass in details to the clod function using a Dictionary, ["username":"", "password":""] etc
PFCloud.callFunctionInBackground