Sign out from apple using flutter - ios

How can I logout from apple using flutter
I use sign_in_with_apple plugin + when I signin with apple and i try to print email I get null
Future<UserCredential> signInWithApple() async {
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
SharedPreferences prefs=await SharedPreferences.getInstance();
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider("apple.com").credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
prefs.setString("email", appleCredential.email.toString());
print("hello $appleCredential");
prefs.setString("mode", "apple");
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context)=>HomePage()), (route) => false);
return await FirebaseAuth.instance.signInWithCredential(oauthCredential);
}

Related

how to clear user session of react-native-app-auth

After logged in by the authorization function of the react-native-app-auth lib I couldn't log in with another account until the token expired, that's because the user's session continues to be used.
OBS: I already tried to clear cookies with #react-native-cookies/cookies
I already tried using revoke, but even if the token is revoked, the user session remains in the webview
The closest to a solution I found was using the authorize function passing a logout url to the WEB application
Tried opening my own webview but logout didn't work
About the server:
Devise v4.7.3 + doorkeeper v.5.4.0 is used
The backend uses Ruby 2.7.1 and Rails 6.0.3.3
App:
React Native v0.66.3
react-native-app-auth 6.0.1
const config = {
issuer: API_URL,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
redirectUrl: CALLBACK_LOGIN,
} as AuthConfiguration
export const login = async () => {
try {
const result = await authorize(config)
const userData = await requestUserInfo(result.accessToken)
await AsyncStorage.setItem(USER_INFO, JSON.stringify(userData.data))
await AsyncStorage.setItem(TOKEN_KEY, result.accessToken)
await AsyncStorage.setItem(
TOKEN_EXPIRATION_DATE,
result.accessTokenExpirationDate
)
return true
} catch (error) {
return false
}
}
export const logout = async () => {
await AsyncStorage.removeItem(USER_INFO)
await AsyncStorage.removeItem(TOKEN_KEY)
await AsyncStorage.removeItem(TOKEN_EXPIRATION_DATE)
}
I tried to revoke the token but it didn't work.
I expected the user to be able to switch accounts after logging out

Flutter Firebase Sign In With Apple Not Working ios

I am trying to set up with apple sign in with the following code. When tapping the button nothing happens.
I get the following error:
NoSuchMethodError (NoSuchMethodError: The method '[]' was called on null.
Receiver: null
Tried calling: []("User"))
The new user appears in firebase authentication but other than that the app does not work.
I have enabled sign in with apple in xcode and on developer.apple.com. I have also tried redownloading my provisioning profiles in xcode but no luck.
I have enabled apple sign in in firebase.
Does anyone know how to fix this?
mixin AuthenticationApple {
static String generateNonce([int length = 32]) {
const charset =
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
final random = Random.secure();
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
.join();
}
/// Returns the sha256 hash of [input] in hex notation.
static String sha256ofString(String input) {
final bytes = utf8.encode(input);
final digest = sha256.convert(bytes);
return digest.toString();
}
static Future<User> signInWithApple({BuildContext context}) async {
User user;
// To prevent replay attacks with the credential returned from Apple, we
// include a nonce in the credential request. When signing in with
// Firebase, the nonce in the id token returned by Apple, is expected to
// match the sha256 hash of `rawNonce`.
final rawNonce = generateNonce();
final nonce = sha256ofString(rawNonce);
// Request credential for the currently signed in Apple account.
final appleCredential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
nonce: nonce,
);
// Create an `OAuthCredential` from the credential returned by Apple.
final oauthCredential = OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
try {
// Sign in the user with Firebase. If the nonce we generated earlier does
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
final UserCredential userCredential =
await FirebaseAuth.instance.signInWithCredential(oauthCredential);
user = userCredential.user;
} on FirebaseAuthException catch (e) {
if (e.code == 'account-exists-with-different-credential') {
ScaffoldMessenger.of(context).showSnackBar(
AppWidget.customSnackBar(
content: 'The account already exists with a different credential.',
),
);
} else if (e.code == 'invalid-credential') {
ScaffoldMessenger.of(context).showSnackBar(
AppWidget.customSnackBar(
content: 'Error occurred while accessing credentials. Try again.',
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
AppWidget.customSnackBar(
content: 'Error occurred using Apple Sign-In. Try again.',
),
);
}
return user;
}
}
I just surpassed this issue by using iOS 13.0 for simulator. It seems there's an issue with higher versions when using the simulator but not with physical devices.
You can download and install simulators all the way back to iOS 13.0 (which is the minimum version for Apple Sign-In. In Xcode:
Xcode > Preferences.
Select the "Components" tab.
Mark the simulators you want.
Press "Check and Install Now".

Using third-parties id providers sign-in credentials to create a user in Aqueduct server

I'm trying to wrap my head around the whole Auth process as I'm building my first API/DB in Aqueduct, but I only found out on docs and other posts how to register users with email/password combination.
#Operation.post()
Future<Response> createUser(#Bind.body() User user) async {
if(user.username == null || user.password == null) {
return Response.badRequest(body: {
"error": "username and password required."});
}
user
..salt = AuthUtility.generateRandomSalt()
..hashedPassword = authServer.hashPassword(user.password, user.salt);
return Response.ok( await Query(context, values: user).insert());
}
In my client app I sign up/in with Google sign-in and use idToken and accessToken that the API to sign-in into Firebase ( which I'm trying to replace with this Aqueduct API ) and I want to do the same in Aqueduct.
Future<User> signInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn().catchError((e){
print('Google sign in error: $e');
});
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
await _firebaseAuth.signInWithCredential(credential);
return _firebaseAuth.currentUser;
}
So I'm looking to sign-in with the same idToken/accessToken returned from Google.
Can you guys point me in the right direction? It seems I can't find any post about this subject.
How would I create a new user this way?
Many thanks.
Cheers.

Getting Gmail API access and ID token, but refresh token is NULL

Following https://developers.google.com/identity/sign-in/web/server-side-flow After getting the authorization code from JavaScript, and passing it to the server side, we indeed get an access token (and an ID token), but not the required refresh token.
There are many posts around but could not solve it yet.
Any suggestion how to get the refresh token?
thanks!
private String getResponseToken(GoogleClientSecrets clientSecrets,
String authCode) throws IOException {
try {
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
// "https://accounts.google.com/o/oauth2/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode, //NOTE: was received from JavaScript client
"postmessage" //TODO: what's this?
).execute();
String accessToken = tokenResponse.getAccessToken();
String idToken = tokenResponse.getIdToken();
//TODO: not getting a refresh token... why?!
String refreshToken = tokenResponse.getRefreshToken();
Boolean hasRefreshToken = new Boolean(!(refreshToken == null));
LOGGER.warn("received refresh token: {}", hasRefreshToken);
LOGGER.debug("accessToken: {}, refreshToken: {}, idToken: {}", accessToken, refreshToken, idToken);
return accessToken;
}catch (TokenResponseException tre){...}
Gmail API only gives the refresh token the first time you ask for the users permission. (At least this is what happens to me).
Go to: https://myaccount.google.com/permissions?pli=1, remove the authorization to your app and run your code. You should receive the refresh token.
you should add the
AccessType = "offline"
You need to call the function
new GoogleAuthorizationCodeRequestUrl(...).setAccessType("offline")
or another syntax:
var authReq = new GoogleAuthorizationCodeRequestUrl(new Uri(GoogleAuthConsts.AuthorizationUrl)) {
RedirectUri = Callback,
ClientId = ClientId,
AccessType = "offline",
Scope = string.Join(" ", new[] { Scopes... }),
ApprovalPrompt = "force"
};
in Fiddler you should see the following request:
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/webmasters&redirect_uri=http://mywebsite.com/google/scapi/callback/&response_type=code&client_id=xxx&access_type=offline
see also here
More details about setAccessType can be found here
after finding how to use the Google APIs at the backend (documentation is somewhat partial..), the issue was fixed at the FrontEnd side by tweaking a parameter:
grantOfflineAccess({
- prompt: 'select_account'
+ prompt: 'consent'
HTH

Why is OAuth2 with Gmail Nodejs Nodemailer producing "Username and Password not accepted" error

OAuth2 is producing "Username and Password not accepted" error when try to send email with Gmail+ Nodejs+Nodemailer
Code - Nodejs - Nodemailer and xoauth2
var nodemailer = require("nodemailer");
var generator = require('xoauth2').createXOAuth2Generator({
user: "", // Your gmail address.
clientId: "",
clientSecret: "",
refreshToken: "",
});
// listen for token updates
// you probably want to store these to a db
generator.on('token', function(token){
console.log('New token for %s: %s', token.user, token.accessToken);
});
// login
var smtpTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
xoauth2: generator
}
});
var mailOptions = {
to: "",
subject: 'Hello ', // Subject line
text: 'Hello world ', // plaintext body
html: '<b>Hello world </b>' // html body
};
smtpTransport.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log('Message sent: ' + info.response);
}
smtpTransport.close();
});
issues:
I used Google OAuth2 playground to create the tokens, https://developers.google.com/oauthplayground/
It looks to grab a valid accessToken ok, using the refreshToken, (i.e. it prints the new access token on the screen.) No errors until it tries to send the email.
I added the optional accessToken: but got the same error. ( "Username and Password not accepted")
I am not 100% sure about the "username", the docs say it needs a "user" email address - I guess the email of the account that created to token, but is not 100% clear. I have tried several things and none worked.
I have searched the options on the gmail accounts, did not find anything that looks wrong.
Also, when I did this with Java, it needed the google userID rather than the email address, not sure why this is using the email address and the Java is using the UserId.
nodemailer fails with a "compose" scope
The problem was the "scope"
it fails with:
https://www.googleapis.com/auth/gmail.compose
but works ok if I use
https://mail.google.com/
Simply just do the following:
1- Get credentials.json file from here https://developers.google.com/gmail/api/quickstart/nodejs press enable the Gmail API and then choose Desktop app
2- Save this file somewhere along with your credentials file
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if(err){
return console.log('Error loading client secret file:', err);
}
// Authorize the client with credentials, then call the Gmail API.
authorize(JSON.parse(content), getAuth);
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if(err){
return getNewToken(oAuth2Client, callback);
}
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function getAuth(auth){
}
3 - Run this file by typing in your terminal: node THIS_FILE.js
4- You'll have token.json file
5- take user information from credentials.json and token.json and fill them in the following function
const nodemailer = require('nodemailer');
const { google } = require("googleapis");
const OAuth2 = google.auth.OAuth2;
const email = 'gmail email'
const clientId = ''
const clientSecret = ''
const refresh = ''
const oauth2Client = new OAuth2(
clientId,
clientSecret,
);
oauth2Client.setCredentials({
refresh_token: refresh
});
const newAccessToken = oauth2Client.getAccessToken()
let transporter = nodemailer.createTransport(
{
service: 'Gmail',
auth: {
type: 'OAuth2',
user: email,
clientId: clientId,
clientSecret: clientSecret,
refreshToken: refresh,
accessToken: newAccessToken
}
},
{
// default message fields
// sender info
from: 'Firstname Lastname <your gmail email>'
}
);
const mailOptions = {
from: email,
to: "",
subject: "Node.js Email with Secure OAuth",
generateTextFromHTML: true,
html: "<b>test</b>"
};
transporter.sendMail(mailOptions, (error, response) => {
error ? console.log(error) : console.log(response);
transporter.close();
});
If your problem is the scopes, here is some help to fix
Tried to add this as an edit to the top answer but it was rejected, don't really know why this is off topic?
See the note here: https://nodemailer.com/smtp/oauth2/#troubleshooting
How to modify the scopes
The scopes are baked into the authorization step when you get your first refresh_token. If you are generating your refresh token via code (for example using the Node.js sample) then the revised scope needs to be set when you request your authUrl.
For the Node.js sample you need to modify SCOPES:
// If modifying these scopes, delete token.json.
-const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
+const SCOPES = ['https://mail.google.com'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
And then the call to oAuth2Client.generateAuthUrl will produce a url that will request authorization from the user to accept full access.
from the Node.js sample:
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});

Resources