Is it possible to use client_credentials grant type for auth0 login in CyPress? - automated-tests

In our project we use Auth0 with Google login and don't use username/password login. We started to write tests for our app, but we can't login in Cypress framework. We need login using Google auth (but it's not possible to do it using request). Also, we don't store token in localStorage, because frontend part uses auth0 react sdk for it.
I tried to find how to login in Cypress using auth0, but found only solutions for username/password login.
Possible solution from this post:
Cypress.Commands.add("login", () => {
cy.clearLocalStorage();
const email = "";
const password = "";
const client_id = "";
const client_secret = "";
const audience = "";
const scope = "";
cy.request({
method: "POST",
url: "",
body: {
grant_type: "password",
username: email,
password,
audience,
scope,
client_id,
client_secret,
},
}).then(({ body: { access_token, expires_in, id_token, token_type } }) => {
cy.window().then((win) => {
win.localStorage.setItem(
`##auth0spajs##::${client_id}::${audience}::${scope}`,
JSON.stringify({
body: {
client_id,
access_token,
id_token,
scope,
expires_in,
token_type,
decodedToken: {
user: JSON.parse(
Buffer.from(id_token.split(".")[1], "base64").toString("ascii")
),
},
audience,
},
expiresAt: Math.floor(Date.now() / 1000) + expires_in,
})
);
cy.reload();
});
});
});
I can get token using client_credentials grant type, but I can't use it in this solution, because it uses id_token instead of the access_token.
Is it possible to use client_credentials grant type for this login? Or should we turn ON username/password login for it?
Request for client_credentials grant type:
curl --request POST \
--url 'https://YOUR_DOMAIN/oauth/token' \
--header 'content-type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=YOUR_CLIENT_ID \
--data client_secret=YOUR_CLIENT_SECRET \
--data audience=YOUR_API_IDENTIFIER
It returns:
{
"access_token":"eyJz93a...k4laUWw",
"token_type":"Bearer",
"expires_in":86400
}

Here's a Cypress test that shows how we handle Keycloak, Okta, and Auth0 in Ionic for JHipster:
https://github.com/jhipster/generator-jhipster-ionic/blob/main/generators/ionic/resources/oauth2/cypress/support/commands.ts

We turned ON username/password way for login, and used UI for login. After the first login, Cypress don't show login page again, because we are already logged.
My solution (we use only username/password without additional info from Auth0):
Cypress.Commands.add('loginAuth0', (username = Cypress.env('auth0_username'), password = Cypress.env('auth0_password')) => {
cy.visit(`/`);
cy.wait(4000); // wait when auth0 modal is loaded (if user is not logged in)
cy.document().then(doc => {
if (doc.querySelector("#username") != null && doc.querySelector("#password") != null) {
cy.get('#username').type(username);
cy.get('#password').type(password);
cy.get('button[name="action"]').click();
} else {
cy.log("User is already logged.");
}
});
});

Related

next-auth x Microsoft Graph API: Where to get the accessToken

I'm building an email marketing automation tool using NextJS, next-auth and Microsoft Graph API. I'm using next-auth's Azure AD B2C provider to authenticate users, and I've been following their docs.
Within the Configuration (Advanced) section of the docs, I've followed the steps to setup an Azure AD api app to communicate with the Microsoft Graph API (to send email on our user's behalf). Now, when a user signs up, an access_token (jwt) is added to my accounts db table. Here it is decoded:
{
"iss": "https://something.b2clogin.com/b03...f94/v2.0/",
"exp": 1664588154,
"nbf": 1664584554,
"aud": "6eb...c5b",
"idp_access_token": "EwB...QI=",
"idp": "live.com",
"name": "Will Despard",
"sub": "1f7...d6c",
"emails": [
"willdespard#outlook.com"
],
"tfp": "B2C_1_signupsignin",
"scp": "mail.send",
"azp": "ff8...f5d",
"ver": "1.0",
"iat": 1664584554
}
The problem is, there is no example of how to setup the Microsoft Graph JS Client with next-auth. For example, according to Microsoft, to create a Microsoft Graph API client, you must do the following:
import { Client } from '#microsoft/microsoft-graph-client';
const client = Client.init({
authProvider: (done) =>
done(
null,
accessToken // WHERE DO WE GET THIS FROM?
),
});
const sendMail = {
message: {
subject: 'Meet for lunch?',
body: { contentType: 'Text', content: 'The new cafeteria is open.' },
toRecipients: [
{ emailAddress: { address: 'william.cm.despard#gmail.com' } },
],
},
};
const userDetails = await client.api('/me/sendMail').post(sendMail);
However, the following is unclear:
Where are we meant to get the accessToken used in this example from? I've tried using the idp_access_token in the decoded accessToken on my accounts db table (above), but this doesn't seem to work.
I'm assuming the accessToken we use to communicate with Microsoft Graph API is going to expire after a short amount of time. How do we handle getting a new token?
Help/code examples would be much appreciated!
I would try it like this. First, it looks that for graph access you should be looking for Azure AD provider, not Azure AD B2C that is a service that provides identity providers. I.e. looks like you need this one: https://next-auth.js.org/providers/azure-ad
To use Microsoft Graph to send mail you'll also need to request a non-default scope with "Send Mail" grant from your user. Means, when authorizing your app the user will be asked to consent that your app will send emails on behalf of him. Also you'll need to save the graph access token you get from the authentication flow. Something like this:
import AzureADProvider from "next-auth/providers/azure-ad"
export const authOptions: NextAuthOptions = {
providers: [
....
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
authorization: {
params: {
scope:
"openid email profile Mail.Send",
},
},
// tenantId: process.env.AZURE_AD_TENANT_ID,
}),
],
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token
},
Please note that if you do not specify tenantId that would mean that your application will be available for users from any tenant, but that in turn would mean that you must be a verified publisher (i.e. must have a valid MPN ID associated with your app). If you do specify a tenantId, then your app will only work for users from that specified tenant.
Later on, you could just use the token from the API:
import { getToken } from 'next-auth/jwt';
import { Client } from '#microsoft/microsoft-graph-client';
// some API function
export default async function handler(req, res) {
const token = await getToken({ req })
if (token) {
const accessToken = token.accessToken;
const client = Client.init({
authProvider: (done) =>
done(null, accessToken)
});
const sendMail = {
message: {
subject: 'Meet for lunch?',
body: { contentType: 'Text', content: 'The new cafeteria is open.' },
toRecipients: [
{ emailAddress: { address: 'william.cm.despard#gmail.com' } },
],
},
};
const userDetails = await client.api('/me/sendMail').post(sendMail);
...

How to implement Wordpress Application Password Authentication in Javascript async fetch?

I'm trying to setup a website using Wordpress as Headless CMS, using the built-in REST API. Using NuxtJS to fetch the data. Now I want to restrict API access so I enabled/created Wordpress Application Password Authentication.
However, I can not seem to find detailed information on how the URL should be assembled with authentication parameters to fetch data from API endpoint.
Credentials have to be added to the URL that's being fetched?
async asyncData ({ $config: { apiUrl, apiUser, apiPassword } }) {
try {
const products = await (await fetch(`${apiUrl}/producten`)).json()
return {
products
}
}
catch (error) {
console.log(error)
}
},
apiUrl, apiUser, apiPassword are currently in nuxtjs.config.js, under publicRuntimeConfig. But 1) they should come in privateRuntimeConfig?
And 2) getting following as return (which is the correct response from the WP Rest API, because I need to pass auth-credentials somewhere, somehow...)
{ "code": "rest_not_logged_in", "message": "You are not currently logged in.", "data": { "status": 401 } }
Solved by adding options to fetch;
const fetchHeaderOptions = {
cache: 'no-cache',
method: 'GET',
credentials: 'omit', //To instead ensure browsers don't include credentials in the request
mode: 'no-cors',
headers: {
'Authorization': 'Basic ' + encode(`${apiUser}` + ":" + `${apiPassword}`),
'Content-Type': 'application/json; charset=UTF-8; application/x-www-form-urlencoded',
},
}
const products = await (await fetch(`${apiUrl}/products`, fetchHeaderOptions)).json()

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>

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.

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

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);
});
}
));

Resources