Github OAuth using Firebase - how to get user's username - firebase

I followed the Firebase's guide on how to authenticate with Github. https://firebase.google.com/docs/auth/web/github-auth
The return result from Firebase's signInWithRedirect method contains the user's displayName and email, etc. However, it doesn't seem to contain user's 'login' username which is the key for invoking most of Github's API calls.
I am sure there is a way to get it, but I just can't seem to find any documentation. Does anyone happen to know how to solve it?

I ended up using Github's API to get user's username with accessToken.

You should be able to get the user's GitHub username through a parameter called "username" (see more here: https://github.com/firebase/firebase-simple-login/blob/master/docs/v1/providers/github.md)
Note: firebase-simple-login was deprecated on October 3th, 2014

You can use get the authenticated user from this GitHub's api
Or if you use octokit javascript rest api client, you can do something like this
octokit = new Octokit({auth: userAccessToken })
octokit.users.getAuthenticated()
.then(result => {
console.log(result.data.login) // this is the username
})
Note: you'll get accessToken after GitHub <-> firebase login
Hope this is helpful!

You can get the username in additionalUserInfo:
const githubProvider = new firebaseClient.auth.GithubAuthProvider();
githubProvider.addScope('read:user');
githubProvider.setCustomParameters({
allow_signup: false,
});
firebaseClient.initializeApp(clientConfig);
async function submit() {
try {
const response = await firebaseClient
.auth()
.signInWithPopup(githubProvider);
console.log(response.additionalUserInfo);
} catch (error) {
alert(error);
}
}

You Can use email to do authorized requests insted username:
Username: mayGitHubEmail#mail.com
Password: accessToken
like this with Postman
body sent
Here is a sample using class func in Swift using Alamofire and SwiftyJSON pods:
import Alamofire
import SwiftyJSON
enum NetworkError: Error {
case url
case server
case auth
}
class GistServices {
class func makePostApiCall(toUrl path: String, withBody parameters: JSON, usingCredentials: Bool = false) -> Result<Data?, NetworkError> {
guard let url = URL(string: path) else {
return .failure(.url)
}
if let email = UserAuthSingleton.shared.get(), let password = UserAuthSingleton.shared.getUserToken() {
var result: Result<Data?, NetworkError>!
var request = AF.request(url, method: .post, parameters: parameters)
if(usingCredentials){
let credentialData = "\(email):\(password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = [HTTPHeader(name: "Authorization", value: "Basic \(base64Credentials)"),
HTTPHeader(name: "Accept", value: "application/json"),
HTTPHeader(name: "Content-Type", value: "application/json")]
request = AF.request(url, method: .post, parameters: parameters.dictionaryValue, encoder: JSONParameterEncoder.default, headers: HTTPHeaders(headers))
}
request
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { (response) in
switch response.result {
case .failure(_):
result = .failure(.server)
case .success(let value):
result = .success(value)
}
}
return result
}
return .failure(.auth)
}
}

Related

NextAuth - Handle refresh token rotation (Reddit) with database

Reddit's access token has an expiration of 1 hour, but I want users that log in to my app to be able to post comments on Reddit for example. This means I need to refresh their access token once it has expired. Since I'm using a database (PlanetScale + Prisma) and not a JWT strategy, the documentation found here https://next-auth.js.org/tutorials/refresh-token-rotation is not useful to me (jwt callback is never called).
As far as I'm understanding it, it means it's not really possible to check the expiration in the session callback and refresh the token here without accessing the database each time?
What can I do if I want to refresh the access token in my database? Should I use a JWT strategy instead, even though I'm using a database?
To do refresh token rotation when using a database strategy you can do something like this:
async function refreshAccessToken(session: Session) {
if (!session.user?.id) {
return;
}
const {
id,
refresh_token: refreshToken,
expires_at: expiresAt,
} = (await prisma.account.findFirst({
where: { userId: session.user.id, provider: "reddit" },
})) ?? {};
if (!id || !refreshToken) {
return;
}
// If expired refresh it
if (expiresAt && Date.now() / 1000 > expiresAt) {
const authorizationString = Buffer.from(
`${process.env?.["REDDIT_CLIENT_ID"]}:${process.env?.["REDDIT_CLIENT_SECRET"]}`,
).toString("base64");
const headers = {
Authorization: `Basic ${authorizationString}`,
"Content-Type": "application/x-www-form-urlencoded",
};
const urlSearchParams = new URLSearchParams();
urlSearchParams.append("grant_type", "refresh_token");
urlSearchParams.append("refresh_token", refreshToken);
urlSearchParams.append("redirect_uri", `${process.env?.["NEXTAUTH_URL"]}/api/auth/callback/reddit`);
const { data } = await axios.post<RedditResponse>("https://www.reddit.com/api/v1/access_token", urlSearchParams, {
headers,
});
await prisma.account.update({
where: { id },
data: {
access_token: data.access_token,
expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
refresh_token: data.refresh_token,
token_type: data.token_type,
scope: data.scope,
},
});
}
}
You can use this anywhere I guess. I don't know if it makes sense to use this in the session callback or not since it's probably a performance hit, so maybe just call it each time you actually need the access token for something? I'm not knowledgable about this to know what the best practice is in this regard...
After many hours of tinkering i just found out how to get the refresh token into the database!
following the first part of the next auth token refresh tutorial, add the authorization param to the provider options
const GOOGLE_AUTHORIZATION_URL =
"https://accounts.google.com/o/oauth2/v2/auth?" +
new URLSearchParams({
prompt: "consent",
access_type: "offline",
response_type: "code",
});
and
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization: GOOGLE_AUTHORIZATION_URL,
}),
This will send me well on my way to figuring out the rest of the process... hope it works for you too!

How can I check username and password of an existing user in WordPress with react-native/

I'm writing a mobile application for a WordPress site.
I want to check username and password with WPAPI and JSON Basic Authentication plugin.
I have tried this code so far:
const wp = new WPAPI({
endpoint: 'https://example.com/wp-json',
username: this.state.username,
password: this.state.password
});
console.log(wp.users().me());
it returns
_options:{auth:true, ...}
no matter what is the username and password value.
Maybe show your react native implementation of the WPAPI.
Documentation:
As an example, wp.users().me() will automatically enable authentication to permit access to the /users/me endpoint.
Sounds like it's more an API-Call which set a flag instead of returning your userprofile (I guess you expected to get your user-profile?!)
Maybe give it a try to request a page (like described in the examples (see: Documentation)):
wp.pages().slug( 'about' )
I have finally solved this problem:
validate() {
let username = this.state.username;
let password = this.state.password;
let userPassCombination = username + ':' + password;
let encodedPass = base64.encode(userPassCombination);
let headers = {
'Content-Type': 'text/json',
Authorization: 'Basic ' + encodedPass,
};
fetch(myConstants.URL + '/wp-json/', {
method: 'GET',
headers: headers,
}).then(responseData => {
//console.log(JSON.stringify(responseData));
if (responseData.ok) {
this.props.navigation.navigate('Profile', {
username: this.state.username,
});
}
else {
alert('wrong information.');
}
});
}
}

403 The caller does not have permission for Firebase Management API addFirebase

I want to add Firebase project through Firebase Management Api. So for that. I made project on Google Cloud Platform console. And created service account with permission as a owner.
I tried to read and create project throw google api explorer for addFirebase and it works. But when i try to do the same through my code it read availableProject successfully and give output as
{
"projectInfo": [
{
"project": "projects/firebase-api-238012",
"displayName": "Firebase-Api"
}
]
}
but when i try to add project it give me this error
{
"error": {
"code": 403,
"message": "The caller does not have permission",
"status": "PERMISSION_DENIED"
}
}
I don't know why its is not creating project. What other permission it needs. And why it allowed to me read available projects first.
here is how i am trying to add my project.
jwt.js
const { google } = require('googleapis');
var serviceAccountJwt = require('./Firebase-Api-b0e41b85ad44.json');
exports.connect = async () => {
return new Promise((resolve, reject) => {
// scope is based on what is needed in our api
const scope = ['https://www.googleapis.com/auth/firebase', 'https://www.googleapis.com/auth/cloud-platform'];
// create our client with the service account JWT
const { client_email, private_key } = serviceAccountJwt;
const client = new google.auth.JWT(client_email, null, private_key, scope, null);
// perform authorization and resolve with the client
return client.authorize((err) => {
if (err) { reject(err) }
else {
resolve(client)
};
});
});
}
index.js file
const { google } = require('googleapis');
const request = require('request');
const { connect } = require('./jwt');
const availableProjects = 'https://firebase.googleapis.com/v1beta1/availableProjects';
async function getAccessToken() {
let client = await connect();
let accessToken = await client.getAccessToken();
let res = await getProjects(accessToken.token)
}
getAccessToken().catch(err => {
console.log(JSON.stringify(err))
})
const bodys = {
"timeZone": "America/Los_Angeles",
"locationId": "asia-south1",
"regionCode": "US"
}
async function getProjects(accesstoken) {
let options = {
url: availableProjects,
headers: {
'Authorization': 'Bearer ' + accesstoken,
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}
return request(options, async function (err, res) {
if (err) {
console.error(err + " error");
} else {
//here it gives successful output
console.log("response")
console.log(res.body);
let bodyJson = JSON.parse(res.body);
let projectName = bodyJson.projectInfo[0].project;
console.log(projectName)
await addProject(accesstoken, projectName)
return res.body;
}
});
}
async function addProject(accesstoken, projecctID) {
fbUrl = getAddFBUrl(projecctID);
let options = {
url: fbUrl,
headers: {
'Authorization': 'Bearer ' + accesstoken,
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body:JSON.stringify(bodys)
}
return request.post(options, function (err, res) {
if (err) {
console.error(err + " error");
} else {
//here in response out put as permission denied 403
console.log("response")
console.log(res.body);
console.log(JSON.stringify(res));
return res.body;
}
});
}
function getAddFBUrl(projectId) {
return 'https://firebase.googleapis.com/v1beta1/' + projectId +
':addFirebase';
}
i found one similar question to this. But it didn't helped me to resolve my issue which is here
AskFirebase
From the Firebase REST reference: Method: projects.addFirebase
To call projects.addFirebase, a member must be an Editor or Owner for
the existing GCP Project. Service accounts cannot call
projects.addFirebase.
Update:
To call projects.addFirebase, a project member or service account must have the following permissions (the IAM roles of Editor and Owner contain these permissions): firebase.projects.update, resourcemanager.projects.get, serviceusage.services.enable, and serviceusage.services.get.
https://firebase.google.com/docs/projects/api/reference/rest/v1beta1/projects/addFirebase
I'm not sure if my answer will be helpful for author of this question, but this if first two things all should check when facing 403 Error with Google Cloud APIs
0) Check configuration with gcloud
1) As mentioned before the first thing is to check the role of service account. You need Editor/Owner usually.
https://cloud.google.com/iam/docs/understanding-roles
https://console.cloud.google.com/iam-admin
2) The second one is to check if API enabled for project at all.
Also when creating a key check it for correct service account.
For someone who's just get started like me, this thing maybe helpful. When I seted up database, I choose Start in locked mode instead of Start in test mode. Therefore, I can't read or write :((. For beginner, just set everything in test mode. Hope it helpful.
https://i.stack.imgur.com/nVxjk.png
Your problem means that your project is not linked with your firebase account which means you have to login with your firebase account. Than you will have the permission
type cd functions in your firebase project directory
type firebase login
login with the Gmail which is connected with your firebase account
It'll work

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.

How to recognise an existing Google OAuth authentication made via Firebase and perform a Google Directory API request in Angular 2?

I want to use my existing authentication and be able to use that same authentication to perform a get request to the Google Directory API. Here's my current code:
login() {
this.firebaseRef = new Firebase('https://xxx.firebaseio.com');
this.firebaseRef.authWithOAuthPopup("google", (error, authData) => {
if (error) {
console.log("Login Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
}, {
scope: "https://www.googleapis.com/auth/admin.directory.user.readonly"
});
}
getData() {
// TO-DO
// Recognise existing OAuth and perform a GET request to
// https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com
}
You could leverage the getAuth method on the firebaseRef instance. Something like that:
getData() {
var authData = this.firebaseRef.getData();
var provider = authData.provider;
// In your case provider contains "google"
}
See this documentation: https://www.firebase.com/docs/web/api/firebase/getauth.html.
I've found the solution. I need to use the current access token in the http request headers for the GET request.
import {Http, Headers} from 'angular2/http';
getData() {
// get access token
let authData = this.firebaseRef.getAuth();
let oauthToken = authData.google.accessToken;
console.log(oauthToken);
// set authorization on request headers
let headers = new Headers();
headers.append('Authorization', 'Bearer ' + oauthToken);
// api request
this.http.get('https://www.googleapis.com/admin/directory/v1/users?domain=nunoarruda.com',{headers: headers}).subscribe((res) => {
console.log(res.json());
});
}

Resources