I'm using next-auth v4, Firebase v9 and NextJS and trying to solve an issue with Firebase security rules.
My security rules do not receive anything in request.auth because I'm using next-auth and I couldn't find a way to pass my next-auth session ID as a UID in firebase requests.
In next-auth, I'm using session callbacks to determine when to create new user in database:
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.JWT_SECRET,
callbacks: {
async session({ session, token }) {
session.id = token?.sub;
const userDocRef = doc(db, "users", session.id);
const userDocSnap = await getDoc(userDocRef);
if (!userDocSnap.exists()) {
await setDoc(doc(db, "users", session.id), {
uid: session.id,
name: session.user.name,
image: session.user.image,
email: session.user.email,
});
}
How can I make it so that I'm able to verify the userId from the firebase side, while still using next-auth. Is there a way to pass session.id from my JWT to firebase?
In the documentation it states:
If your app uses Firebase Authentication or Google Cloud Identity Platform, the request.auth variable contains the authentication information for the client requesting data.
Firebase security rules only receive user information when using Firebase Authentication or Google Cloud Identity. It cannot be made to work with other auth systems. The UID of the currently signed in user is always provided securely by the Firebase SDK. There is no way to "pass" a UID into security rules - that would not be secure at all, as it would be easy to fake the user.
Perhaps you could use some sort of custom authentication implementation of your creation to bridge between what you have now and Firebase. You will still need to use the Firebase Auth SDK to sign the user in.
Related
We are having a flutter app (ios, android, web), where users are signed in via username & password.
We are also using google firebase since its powerful and easy to integrate.
The username and password mainly belongs to the website where we are gathering data at. (As example - If they use the website without the app, and they change the password, after that he wont be able to login to the app)
Now the mentionned websites host is giving us API access, login via OpenId to get the access token for the API. Because we are a safety risk since we store the passwort of the users too!
For the API access we dont really need to store Username and password of the user, since they are redundant anyway. But if we want to add a feature (for example message sending or further data storage) we need to have the user signed in into firebase.
Upt to now we are using for (first) signin the following snippet:
firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
and for already signed in users :
firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
Notice that similar credentials are also using to login on the API. (Since the user is there already registered)
How can we login on firebase with said information without asking twice for a password ond username (once for us, and once for the API) ?
We already tried :
await firebaseAuth.signInWithCustomToken(token)
with the jwl token from the OpenId, of course it did not work because the token did not contain the uid reference.
SOLUTION
Create a Firebase Cloud Function just like described in Firebase Cloud Functions.
Be aware that if you want to create a customtoken, the cloud functions need rights. On initializeApp(..)
admin.initializeApp({
serviceAccountId: '{App_Name}#appspot.gserviceaccount.com',
});
So the correct service account has to be selected, you also have to give him the rights to generate tokens. (See => Stackoverflow Question
The Cloud Function does then look the following way :
export const functionName= functions.https.onRequest(async (request, response) => {
const id = request.query.id;
const passcode = request.query.passcode; // not really needed
// add other passcodes for different authentications
if (passcode == "{COMPARE SOMETHING}") {
await admin.auth().createCustomToken(id).then((customToken) => {
response.status(200).send({
'id': id,
'customToken': customToken
});
}).catch((error) => {
response.status(500).send({
'ErrorMessage': "No token could be generated",
"Error": error
});
});
}
else {
response.status(500).send({
'ErrorMessage': "Passcode wrong"
});
}
});
On the other hand we have the code on the mobile app :
// Get JWT Token
Map<String, dynamic> jwtpayload =
Jwt.parseJwt(response_decoded['id_token']); // use import 'package:jwt_decode/jwt_decode.dart';
final queryParameters = {
'id': jwtpayload ['sub'],
'passcode': 'APassCode',
};
final uri = Uri.https('us-central1-{yourApp}.cloudfunctions.net',
'/{functionName}', queryParameters);
final cloud_function_api_call = await client.post(uri);
var decoded_cloud_function_api_call =
jsonDecode(cloud_function_api_call.body);
And at the end :
await firebaseAuth.signInWithCustomToken(
decoded_cloud_function_api_call['customToken']);
I hope it helps others facing a similar issue.
I have been trying to implement sign in with google and apple using the following libraries and supabase in bare react-native projects.
react-native-apple-authentication
react-native-google-signin
They work very well with firebase as described here using firebase sdk.
The concept is fairly simple, the native sdk for both apple and google sign return user credential after a successful sign in . Here is an example from https://rnfirebase.io/auth/social-auth
With google credential
import auth from '#react-native-firebase/auth';
import { GoogleSignin } from '#react-native-google-signin/google-signin';
async function onGoogleButtonPress() {
// Get the users ID token
const { idToken } = await GoogleSignin.signIn();
// Create a Google credential with the token
const googleCredential = auth.GoogleAuthProvider.credential(idToken);
// Sign-in the user with the credential
return auth().signInWithCredential(googleCredential);
}
With apple credential
import auth from '#react-native-firebase/auth';
import { appleAuth } from '#invertase/react-native-apple-authentication';
async function onAppleButtonPress() {
// Start the sign-in request
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
requestedScopes: [appleAuth.Scope.EMAIL, appleAuth.Scope.FULL_NAME],
});
// Ensure Apple returned a user identityToken
if (!appleAuthRequestResponse.identityToken) {
throw 'Apple Sign-In failed - no identify token returned';
}
// Create a Firebase credential from the response
const { identityToken, nonce } = appleAuthRequestResponse;
const appleCredential = auth.AppleAuthProvider.credential(identityToken, nonce);
// Sign the user in with the credential
return auth().signInWithCredential(appleCredential);
}
I have gone through the auth provided by supabase here supabase-js auth
The closest thing I can find related to the implementation above is the use of a refresh-token as shown here
Sign in using a refresh token
// An example using Expo's `AuthSession`
const redirectUri = AuthSession.makeRedirectUri({ useProxy: false });
const provider = 'google';
AuthSession.startAsync({
authUrl: `https://MYSUPABASEAPP.supabase.co/auth/v1/authorize?provider=${provider}&redirect_to=${redirectUri}`,
returnUrl: redirectUri,
}).then(async (response: any) => {
if (!response) return;
const { user, session, error } = await supabase.auth.signIn({
refreshToken: response.params?.refresh_token,
});
});
The example above uses a refreshToken parameter auth.sign() to create a session for a user who is already registered.
Correct me if am wrong. According to the auth documentation there is no method that allows one to create a user using an auth credential. This is because of how gotrue social auth is implemented. The only solution I can think of as now is to move the entire auth logic to the backend and handle it on the web. This is not elegant at all since it will have to involve ugly deep-linking or webview implementations to use supabase session and access token.
Given the scenario above, is there way of using native social sdk in react-native to create a new user account in supabase using a user credential without having to force them use an email link, password or web ? Any ideas , answers or corrections are welcome.
At the time of writing, Signing in with third party credentials is not implemented in gotrue(Module used for auth in supabase) yet. If you have a native app, flutter app and react-native app that needs social auth using credentials follow up on this WIP:PR. For further information refer to Proof Key for Code Exchange (PKCE).
I am using firebase google auth to manage users. Further, I have to integrate the users calendar using the calendar API, now firebase auth provides ID Token, ID Refresh token & oAuth token but not oAuth refresh token, due to which I will have to obtain the oAuth token & oAuth refresh token using gAPI, now I got that, but is there a way to use those tokens to create a firebase auth user? I know there is method to create sign in with signInWithCredential but this takes ID Token and not oAauth token.
UPDATE
Sign In code:
const { client_secret, client_id, redirect_uris } = credentials.web;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
// Get code from the URL
oAuth2Client.getToken(code, (err, token) => {
oAuth2Client.setCredentials(token);
});
After gAPI signin, I am getting following details:
{
"access_token": "xxx",
"refresh_token": "xxx",
"scope": "https://www.googleapis.com/auth/calendar",
"token_type": "Bearer",
"expiry_date": 1613023105889
}
To access the ID Token, you have to add additional scope to the GAPI OAuth request. Along with your calendar scope, add "openid". Then in the token response, you should have access to id_token.
Also, you can skip the above step and exchange the OAuth token from google with firebase to get firebase ID Token.
Official docs: https://firebase.google.com/docs/reference/rest/auth/#section-sign-in-with-oauth-credential
I am using flutter with firebase to manage my users, and in this link, it says you can specify the uid during user creation: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
My question: What's the equivalent in dart/ flutter? I understand firebase auto-generates one for you, but in my use case I need to be able to specify mine.
For flutter, I am only aware of createUserWithEmailAndPassword method but it does not have a 'uid' argument.
FirebaseAuth.instance.createUserWithEmailAndPassword(email: null, password: null)
In the link above, however, they provided an example (node.js) with such methods.
admin.auth().createUser({
uid: 'some-uid',
email: 'user#example.com',
phoneNumber: '+11234567890'
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch(function(error) {
console.log('Error creating new user:', error);
});
You can fully control the creation of Firebase Authentication users by implementing a custom provider. But as you can probably imagine, this is a sensitive operation, so the code requires that you have full administrative access to the Firebase project. For that reason, you can't run this type of operation in your Flutter app, but must run it on a trusted environment, such as your development machine, a server you control, or Cloud Functions.
Typically this means:
you'll gather the user's credentials in your Flutter app
send them (securely) to a custom endpoint on the server
and there validate the the user credentials are correct
and use the Admin SDK to create a token for the user
that you then send back securely to the Flutter app
There is no such option for any of the Firebase the client SDKs on any platform (Android, iOS, web, Flutter, etc).
Firebase Admin is a server SDK, and trusts that the code calling its methods is privileged and running in a secure and trusted environment. This is considered to be safe for inventing UIDs. The client SDKs are run on user devices, which is considered untrusted and could be compromised. In that case, the Firebase Auth product has to come up with an appropriate UID for the user.
Use the firebase cloud functions
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.createUser1 = functions.https.onCall(async (data, _context) => {
try {
const user = await admin.auth().createUser({
uid: data.uid,
phoneNumber: data.phoneNumber,
disabled: false,
}); return {response: user};
} catch (error) {
throw new functions.https.HttpsError("failed to create a user");
}
});
then request from flutter app
{
"data":
{
"uid": "12345678",
"phoneNumber": "+905378227777",
"disabled": false
}
}
I am looking for the Firebase to be authenticated seamlessly across multiple projects with different auth providers.
Example.
Suppose I have a website with 10 pages, with different data coming from 2 firebase projects ( project-1, project-2)
project-1 contains all the Users accounts
When I sign up/Login into firebase project (project-1). I am using Google/Phone/Password/Facebook. this creates users account if not exists.
Now I can see the data coming from the firebase (project-1).
Now few of my pages website want to access data from the project-2.
How to maintain the authentication state across the 2 projects so that, project-2 will get authenticated seamlessly with out prompting signup again. Probably with the same auth token which already created for project-1
I read this blog which was created by Ian Barber Working with multiple Firebase projects in an Android app. Which is only discussed about the google and android case only.
Thanks.
You can use a combination of firebase-admin on a server and a custom firebase token as described here.
Essentially, you can get a user's JWT token for your first app using the methods described in this answer. Then you could make a request to your own server that would validate that your JWT token originated from your first project and could then issue you a second JWT token you can use to authenticate with the second project.
I think you can do the following:
Configure an OIDC provider on project-2 that points out to the firebase auth from project-1 .
The issuer should be https://securetoken.google.com/<project-1-id>
The client id its the project-1-id
After you signIn on project-1 you can use the idToken from that app, to login with an OAuthProvider to the second app (project-2):
const provider = new OAuthProvider('oidc.project-1');
const credential = provider.credential({
idToken: 'idTokenFromProject1',
});
signInWithCredential(auth, credential)
idTokenFromProject1 can be fetched through getIdToken() method on the project-1 app
This is now easily possible with OIDC configuration (reference answer by Alenjadro Barone above )
Configure an OIDC provider on project-2 that points out to the firebase auth from project-1 .
The issuer should be https://securetoken.google.com/<project-1-id>
Then You can follow Code below . Its starter but definitely works : -
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
// Create secondary firebase app Instance
FirebaseApp secondaryApp = Firebase.app('secondary');
class Auth {
// default Firebase Auth app instance: PROJECT 1
final firebaseAuth = FirebaseAuth.instance;
// Secondary Firebase App Instance : PROJECT 2
FirebaseAuth secondaryFirebaseAuth =
FirebaseAuth.instanceFor(app: secondaryApp);
signin() async {
// Sign in method of your choice from PTOJECT 1
await firebaseAuth.signInWithEmailAndPassword(
email: "abc#abc.com",//registered user email
password: "password",//registered user password
);
// Declare OAuth Provider for PROJECT 2
// name of OIDC provider as set in Project
final provider = OAuthProvider('oidc.provider');
try {
// get IdToken for signedin user
final userIdToken = await firebaseAuth.currentUser!.getIdToken();
// Get access Token for User using IdTokenResult
final idTokenResult = await firebaseAuth.currentUser!.getIdTokenResult();
final userAccessToken = idTokenResult.token;
// Create OAuthCredentials with idToken & AccessToken
final credential = provider.credential(
idToken: userIdToken,
accessToken: userAccessToken,
);
// Sign in to Secondary app or PROJECT 2
await secondaryFirebaseAuth.signInWithCredential(credential);
} catch (e) {
if (kDebugMode) {
print("This is error $e");
}
}
}
}