node.js, passport-wordpress: The required "redirect_uri" parameter is missing - wordpress

Trying to create a demo using passport-wordpress
https://www.npmjs.org/package/passport-wordpress
passport-wordpress allows you to login to a node.js app using your credentials at wordpress.com
I set up my Wordpress app at developer.wordpress.com/apps:
OAuth Information
Client ID <removed>
Client Secret <removed>
Redirect URL http://greendept.com/wp-pass/
Javascript Origins http://wp-node2.herokuapp.com
Type Web
Request token URL https://public-api.wordpress.com/oauth2/token
Authorize URL https://public-api.wordpress.com/oauth2/authorize
Authenticate URL https://public-api.wordpress.com/oauth2/authenticate
In my node.js app:
var CLIENT_ID = <removed>;
var CLIENT_SECRET = <removed>;
passport.use(new WordpressStrategy({
clientID: CLIENT_ID,
clientSecret: CLIENT_SECRET
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate({ WordpressId: profile.id }, function (err, user) {
return done(err, user);
});
}
When I try to authorize, it goes to this URL (as one line, I've divided into two here for readability):
https://public-api.wordpress.com/oauth2/authorize?
response_type=code&redirect_uri=&client_id= removed
I can see that the redirect_uri is missing in that URL, so it's not surprising that I get this error:
Invalid request, please go back and try again.
Error Code: invalid_request
Error Message: The required "redirect_uri" parameter is missing.
Not sure where or how in my code I should be submitting the redirect_uri.

You need to pass a callback url as option.
From passport-wordpress
The strategy requires a verify callback, which accepts these credentials and
calls done providing a user, as well as options specifying a client ID,
client secret, and callback URL.
And from lib/strategy.js
Examples:
passport.use(new WordpressStrategy({
clientID: '123-456-789',
clientSecret: 'shhh-its-a-secret',
callbackURL: 'https://www.example.net/auth/wordpress/callback'
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate(..., function (err, user) {
done(err, user);
});
}
));

Related

how to handle expiring access token from partner api

I'm new to Next js. I'm sure this is a common issue but I don't know what to search for. Here's an outline:
One of my partners has an API with Bearer auth. The Bearer token comes from an endpoint I call (/auth) with my username and password. That endpoint returns the Bearer token that I use for all other endpoints, but it expires in one day.
How would I handle making API calls on Next.js API routes to this partner? I.e. where would I store this access token so each API route doesn't need to constantly fetch it. And, how do I update it when it expires?
Your clients (once authenticated) should be the ones "storing" these tokens. You would basically need to fetch it from the client's session, cookie, or however you are storing those.
As far as updating these tokens, your auth provider should also provide a "refresh token" that can be used to retrieve a new jwt once it has expired. The purpose here is that you'll be able to refresh the token for the user without requiring them to log in again.
Depending on your provider, this may be a new endpoint you'll need to call.
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
If you are making calls to your partner's api when you go to a specific route(like a protected one), then you should store your access token as cookie with http-only flag to avoid security issues like xss attacks that can steal the session data from your browser(the http-only flag should be set in the /auth route in your partner's api when he sends the response with the token), so make the calls within the getServerSideProps sending the cookie with the request, so your partner should take the token from the cookie and validate it to allow the request, one thing i need to point out is that you can't access an http-only cookie from client side, but as you are using nextJs you can still access it from getServerSideProps with a library called nookies, so you could do something like this:
export const getServerSideProps: GetServerSideProps = async ctx => {
const cookies = nookies.get(ctx)
const someApiData = await fetchApiData(cookies)
if (!someApiData) {
return {
redirect: {
// Redirect to home if not authorized
destination: '/',
permanent: false
}
}
}
return {
//return data fetched from the api
props: {
someApiData
}
}
}
and the function that makes the api call, could look like this(note that i'm using axios):
const fetchApiData = async (cookies: CookieData) => {
try {
const result = await axios.get<ApiData>('/some/api/route', {
// Pay attention to this line, here we are sending the cookie with the access token
headers: {
Cookie: `token=${cookies.token}; HttpOnly;`
}
})
return result.data
} catch (error) {
console.log(error)
}
}
Note that you should send the cookie whenever you make a request to a protected route and your partner should validate this token in each route that he wants to protect.
And to refresh the token without login again, your partner can implement a refresh token like #SLee mentioned. This is just an example but you got the idea.

How to use my firebase authentication to work with external services?

Ok so I am using firebase as authentication for my iOS app. Now I plan on adding video calling to my app using an external service know as connectyCube. This service has their own authentication system and I cannot use their services unless a user is authenticated.
Option 1: I can use their own authentication which means my app would have two authentication systems - not very productive
Option 2: They say I can use an existing authentication to validate users
I understand that this is a common thing in the developers world and I see the word OAuth and JWT being thrown around but I am a rookie developer and I want to understand how I can use firebase and authenticate a user from an external service.
These are the questions they have asked when I opted for the "I have my own authentication" option:
What is your end point URL
Is it GET or POST
Request Headers
Request Params
Response Params
Where do I get all this information from firebase? Any help would be great
As an alternative to #Dharmaraj's answer, you could instead make use of a HTTP Event Cloud Function for this based on the code sample they've provided.
Using this method, you create the endpoint /verifyUserToken to be used by ConnectyCube.
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
export const verifyUserToken = functions.https.onRequest((req, res) => {
const idToken = req.query.token;
verifyUser(idToken)
.then(
(userData) => {
res.status(200).json(userData)
},
(err) => {
console.log("Token verification failed.", err.code || err.message);
res.status(422).json({error: "User token is invalid"})
}
)
.catch((err) => console.error("Unexpected crash", err));
});
async function verifyUser(token) {
if (!token)
throw new Error("token missing");
// using `true` here to force token to be checked against the Firebase
// Auth API rather than trusting its contents as-is
const { uid, email } = await admin.auth().verifyIdToken(token, true);
// pull the user's username from their user data
// at /users/{userId}/username
const username = (await admin.database().ref("users/" + uid + "/username")).val();
// use user's actual email if available, otherwise fallback
// to a userID based email
const uEmail = email || uid + "#users.noreply.yourapp.com";
// use user's username if available, otherwise fallback to
// the email address above.
const uLogin = username !== null ? username : uEmail;
return {
uid,
login: uLogin,
email: uEmail,
user: {id: uid, login: uLogin, email: uEmail}, // <- this part in particular is used by ConnectyCube
users: [{uid, login: uLogin, email: uEmail}]
};
}
Once deployed, you would use the following settings:
Setting
Value
API URL:
https://us-central1-PROJECT-ID.cloudfunctions.net/verifyUserToken
GET/POST
GET
Request params:
{"token": "#{login}"}
Response params:
{"uid": "#{user.id}", "email": #{user.email}, "login": "#{user.login}"}
It looks like ConnectyCube uses some sort of Session Tokens as mentioned in their documentation with their own username and password.
The most easiest way would be creating a ConnectyCube account whenever a new user signs up in your Firebase app using Firebase Auth Triggers for Cloud functions. Then you can generate username and password on behalf of your user and store them in a Database.
So whenever you need to create a ConnectyCube session, check for the currently logged in user and fetch their ConnectyCube credentials.
async function createCCSession() {
const userId = firebase.auth().currentUser.uid
const ccCrednetials = (await firebase.database().ref(`ccCreds/${userId}`).once('value')).val()
ConnectyCube.createSession(ccCredentials)
.then((session) => {
console.log(session)
return session
}).catch((error) => console.log(error));
}
You can protect the database using security rules so a user can access their credentials only.
{
"rules": {
"ccCreds": {
"$uid": {
".read": "$uid === auth.uid"
}
}
}
}
While I don't normally double-answer a question, in the course of exploring some other authentication related problems, I've managed to eliminate the Cloud Function from my other answer entirely and instead call the Authentication API directly.
Setting
Value
API URL:
https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=FIREBASE_CONFIG_API_KEY
GET/POST
POST
Request params:
{"idToken": "#{login}"}
Response params:
{"uid": "#{users.0.localId}", "email": #{users.0.email}, "full_name": "#{users.0.displayName}"}
On your client, you just call the ConnectyCube Login API with the following data:
POST https://api.connectycube.com/login
login=<Firebase-ID-token>
password=<any-random-value-to-pass-the-validation>

Firebase client error: Custom token corresponds to a different audience

I'm using the Firebase Python AdminSDK to generate a custom token which a Javascript client uses to sign in to Firebase. When the JS client tries to authenticate with the custom token it gets the error "Custom token corresponds to a different audience".
The code given with the error: 'auth/custom-token-mismatch'.
Many Google'd answers regarding the "audience" mismatch reference Analytics. But I'm doing a Web project, not iOS or Android, so I can't use Analytics to manage audiences.
The SO answers I've read are listed at the end, below.
I captured the custom token and plugged it in to https://jwt.io/ and both the values and the instanciation/expiration times (an hour apart) look good:
Decoded custom token on jwt.io:
{
"claims": {},
"uid": "<myuniqueID",
"sub": "firebase-adminsdk-1knpr#firebase-<myproject>.iam.gserviceaccount.com",
"iss": "firebase-adminsdk-1knpr#firebase-<myproject>.iam.gserviceaccount.com",
"iat": 1540153710,
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"exp": 1540157310
}
Python server:
def getFirebaseInstance(): # For Firebase Python SDK
try:
currentDir_path = os.path.dirname(os.path.realpath(__file__))
cred = credentials.Certificate(currentDir_path + '/includeFirebaseServiceAccounts/firebase-<myprojectname>-firebase-adminsdk-1knpr-e1244dd261.json')
firebaseAdmin = firebase_admin.initialize_app(cred, { 'databaseURL': 'https://<myprojectname>.firebaseio.com', 'databaseAuthVariableOverride': {'uid':'<myuniqueServerID>'}})
if firebaseAdmin:
return(firebaseAdmin)
except:
raise
def firebaseClientToken(request):
try:
uid = "<myuniqueClientID>" # case sensitive
additional_claims = { }
token = auth.create_custom_token(uid,additional_claims)
return HttpResponse(token)
except Exception as err:
return HttpResponse("System error:" + str(err), status=406)
Javascript client:
(
function authClient2Firebase() {
$.ajax({
url: "firebaseClientToken/",
method: "POST",
success: function(response) { step2(response); },
error: function(xhr) { alert("There was an error loading a security check. Have you lost your internet connection? Detail:" + xhr.responseText); }
});
function step2(customToken) {
try {
firebase.auth().signInWithCustomToken(customToken).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
alert("There was an error with the secure login. \n\nDetail: " + errorMessage + '\nCode: ' + errorCode);
});
}
catch(err) {
alert(err);
}
console.log("authClient2Firebase.js: Firebase login succeeded!");
}
}
)();
My project under the Console "Settings" page does have a Web API key, but I don't see anywhere that it's used.
There's only one user, me, under the Console's "Settings"->"Users and Permissions" page.
There's only one service account listed on the Console "Settings"->"Service Accounts" page. I tried deleting all secrets on that page, generating a new one, then generating and installing a new blue-button "secret" (bad name, actually it generates a whole json credential object).
These are the domains listed in Console "Authentication" -> "Sign-in Method":
localhost Default
<myproject>.firebaseapp.com Default
127.0.0.1 Custom
auth.firebase.com Custom
The actual domain I'm using is localhost:8000, which can't be entered here.
SO answers consulted unsuccessfully:
The custom token corresponds to a different audience
(I'm not using a key, except what's stored in the ServiceAccount
credentials.)
Firebase token error, "The custom token corresponds to a different audience."
Firebase custom auth issue token different audienceenter
link description here (Close, but I'm not using a Node server and
not sure what he means by server "must belong to the same project"
since the Python server isn't registered in any way except through the
ServiceAccount credentials which I downloaded.)
Embarrassing but true, it turned out to be a simple oversight. When the JS client initialized itself as a Firebase app, before authenticating, it was using old credentials from a test environment.
// Initialize Firebase
var config = {
apiKey: "<WebAPI from Firebase console, 'Project Settings'>",
authDomain: "<myproject>.firebaseapp.com",
databaseURL: "https://<myproject>.firebaseio.com",
projectId: "<myproject>",
storageBucket: "<myproject>.appspot.com",
messagingSenderId: "<id from Console Project Settings>" // optional
};
firebase.initializeApp(config);

Firebase refresh id tokens for server authentication

I have a project using firebase where I use firebase id token to verify user on back-end.
Actually, on client, when a user login I use getIdToken(true) and attach it to my requests header.
On server for each request I use verifyIdToken to check if the user is logged in and it's valid.
The problem is that after some time the token expire and the backend is not able to verify the user.
My question is: how to get this work?
I think about using a request interceptor from client side to get an id token for each request but i don't know if this is a good practice and if I have to invalidate the other tokens too with admin.auth().revokeRefreshTokens(userId).
Thanks in advance.
client
axios.interceptors.request.use(function (config) {
if (!firebase.auth().currentUser) {
return config
}
return firebase.auth().currentUser.getIdToken(true).then(token => {
config.headers.Authentication = token
return config
})
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
server
let decodedToken = await admin.auth().verifyIdToken(token);
let userId = decodedToken.uid;

Get Firebase Access Token in POSTMAN

In my web application, I am using Firebase for Authentication, to access any API, I have to authenticate from firebase.
Question:
How can I get access token of firebase in Postman?
I have 2 solutions for this problem:
1) Get Access Token from firebase in postman, store that access token in postman global env. variable and then I can do other API request. (Here I don't know how to get access token in postman)
2) Do the login in the browser, copy access token from network request, store it in bash_profile and then use it in Postman. (Here I don't know how to read OS env. variable)
When you want to use Postman only and don't want to build a frontend you can use this auth request in Postman: POST https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={API_KEY}
In the Body you should send the following JSON string:
{"email":"{YOUR_EMAIL_ADDRESS}","password":"{PASSWORD}","returnSecureToken":true}
Content type is application/json (will be set automatically in Postman).
You can find the Firebase API_KEY in the Firebase project settings (it's the Web-API-key).
As response you will get a JSON object and the idToken is the token you need for all your API requests as Bearer token.
To have a automated setting of this token, you can add the following code in the Tests tab at your auth request:
var jsonData = JSON.parse(responseBody);
postman.setGlobalVariable("id_token", jsonData.idToken);
For all your API requests you should set the Authorization to Bearer Token and the value for the token is {{id_token}}.
Now the token will be automatically used once you executed the auth request and got the response.
An easy way to retrieve the access token from firebase is to:
create an html file in a directory
copy in the html file the content of firebase auth quickstart
replace the firebase-app.js and firebase-auth.js as explained in firebase web setup to point them at the proper cdn location on the web
replace firebase.init script with the initialization code from your app on the console like this:
var config = {
apiKey: "my secret api key",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
projectId: "myapp-bookworm",
storageBucket: "myapp.appspot.com",
messagingSenderId: "xxxxxxxxxxxxx"
};
firebase.initializeApp(config);
open the html file in your browser and either sign in or sign up. The Firebase auth currentUser object value should be displayed.
inspect the html and expand the quickstart-account-details element. This should have the json object displayed.
copy the content of accessToken
In postman go to authorization, select bearer token and paste the copied token in the token value field.
You should be now able to call apis that are secured by firebase auth. Keep in mind that this only gets and passes the access token so once the token is expired you may need to request a new one (steps 5 to 8)
you can also look at this
Hope this helps!
In addition of naptoon's post:
var jsonData = JSON.parse(responseBody);
postman.setGlobalVariable("id_token", jsonData.idToken);
This is "old style", which is deprecated by Postman.
The "new style" is:
pm.environment.set("id_token", pm.response.json().idToken);
go to the pre-request script and add this code (use your API_KEY, USER_EMAIL, USER_PASSWORD)
const reqObject = {
url: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={API_KEY}", // API_KEY -> your API key from firebase config
method: 'POST',
header: 'Content-Type:application/json',
body: {
mode: 'raw',
raw: JSON.stringify({ "email": {USER_EMAIL}, "password": {USER_PASSWORD}, "returnSecureToken": true })
}
};
pm.sendRequest(reqObject, (err, res) => {
const idToken = res.json().idToken; // your idToken
pm.environment.set("FIREBASE_TOKEN", idToken ); // set environment variable FIREBASE_TOKEN with value idToken
});
this code will add the environment variable FIREBASE_TOKEN, but u can do whatever you want with idToken =)
I came across a need to do this where staging and production environments require a different Firebase idToken but local does not use one. I expanded upon naptoon's and leo's answers to use the identitytoolkit's verifyPassword endpoint as part of a pre-request:
const apiKey = pm.environment.get('api_key');
if ( ! apiKey) {
return
}
const tokenEnv = pm.environment.get('token_env')
if (tokenEnv && tokenEnv === pm.environment.name) {
const tokenTimestamp = Number.parseInt(pm.environment.get('token_timestamp'), 10)
const elapsed = Date.now() - tokenTimestamp
if (elapsed < 20 * 60000) {
return
}
}
pm.sendRequest({
url: `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=${apiKey}`,
method: 'POST',
header: {
'Content-Type': 'application/json',
},
body: {
mode: 'raw',
raw: JSON.stringify({
email: pm.environment.get('auth_username'),
password: pm.environment.get('auth_password'),
returnSecureToken: true,
}),
},
}, function (err, res) {
let json
if ( ! err) {
json = res.json()
if (json.error) {
err = json.error
}
}
if (err) {
pm.environment.unset('auth_token')
pm.environment.unset('token_env')
pm.environment.unset('token_timestamp')
throw err
}
pm.expect(json.idToken).to.not.be.undefined
pm.environment.set('auth_token', json.idToken)
pm.environment.set('token_env', pm.environment.name)
pm.environment.set('token_timestamp', Date.now())
})
The access token is cached for a given environment for up to 20 minutes (I have not implemented refresh token). The token is cleared if the environment is different to the last request or an error occurs.
Copy the below block of code and place it in the 'pre-request scripts' tab of the request on Postman. It will automatically get a token and put it as 'Authorization' header every time you make a request. You don't need to add any header or authorization manually. You don't even need to worry about token expiry.
Obviously, replace the app api key, username and password place holders.
const postRequest = {
url: 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={APP_API_Key}',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
body: {
mode: 'raw',
raw: JSON.stringify({
"email": "{Your_Email}",
"password": "{Your_Password}",
"returnSecureToken": true
})
}
};
pm.sendRequest(postRequest, (error, response) => {
var jsonData = response.json();
pm.globals.set("id_token", jsonData.idToken)
});
pm.request.headers.add({key: 'Authorization', value: '{{id_token}}'})
Firebase Auth not response Access Token just idToken. you must verify/exchange with your auth system to get it.
Here is the full list apis I found for interacting with Firebase by using its API endpoint directly.
https://www.any-api.com/googleapis_com/identitytoolkit/docs/relyingparty
If your using Node here's my solution,
With the firebase admin SDK import that into your file, and follow #naptoon instructions for setting up a route in PostMan.
In Nodejs in your file put the following
const user = admin.auth().verifyIdToken(req.headers.authorization)
I tried using
const auth = getAuth() const user = auth.currentUser
and that way didn't work for me so I went with the firebase admin route which worked well with minimal code
For anyone still a bit confused, this works perfectly with Firebase using Auth emulators.
Brief Overview
Create functions
Setup emulator
Generate Token
Perform authed request(s)
1. Create functions
2 functions are required:
Generate ID Token function:
import {https} from "firebase-functions";
import {auth} from "firebase-admin";
export const generateAuthToken = https.onCall((data, _context) => {
if (!data.uid) {
return new https.HttpsError("invalid-argument", "Missing UID argument", "Missing UID argument");
}
return auth().createCustomToken(data.uid).then(value => {
console.log(`Token generated: ${value}`);
return {
status: true,
token: value
};
}).catch(reason => {
console.warn(reason);
return {
status: false,
token: ""
}
});
});
(optional) Auth'd function:
import {https} from "firebase-functions";
import {auth} from "firebase-admin";
export const checkAuthenticated = https.onCall((_data, context) => {
if (!context.auth) {
return new https.HttpsError("unauthenticated", "You need to be authenticated to retrieve this data");
}
return "Congratulations! It works.";
});
2. Setup environment
(optional) Setup emulators
Run your firebase project as you'd normally do
Postman, create 2 requests:
1. generateAuthToken
Method: POST
URL: http://127.0.0.1:5001/{project-name}/{region}/generateAuthToken
Headers:
"Content-Type": "application/json; charset=utf-8"
body (RAW: JSON)
{
"data": {
"uid":"1234567890"
}
}
2. checkAuthenticated
Method: POST
URL: http://127.0.0.1:5001/{project-name}/{region}/checkAuthenticated
Headers:
"Content-Type": "application/json; charset=utf-8"
body (RAW: JSON)
{
"data": {
}
}
Authentication Tab > Type Bearer: {insert token}
3. Generate Token
Call postman function using method described in 2.1
4. Perform authed request(s)
For every authed request, add the bearer token as described in 2.2 and it all works as expected.

Resources