Firebase authentication with custom token - ruby-on-rails

I have a firebase project which Im trying to authenticate from my rails server creating a custom token with the library ruby-jwt as it says on the docs, but i keep getting the same error:
auth/invalid-custom-token, The custom token format is incorrect. Please check the documentation.
The credentials.json is from the service account I made in google console, uid is sent from the front end to the api.
def generate_auth_token(uid)
now_seconds = Time.now.to_i
credentials = JSON.parse(File.read("credentials.json"))
private_key = OpenSSL::PKey::RSA.new credentials["private_key"]
payload = {
:iss => credentials["client_email"],
:sub => credentials["client_email"],
:aud => 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit',
:iat => now_seconds,
:exp => now_seconds+(60*60), # Maximum expiration time is one hour
:uid => uid.to_s,
:claims => {:premium_account => true}
}
JWT.encode(payload, private_key, 'RS256')
end
it looks like this in jwt.io
{
"iss": "defered#defered.iam.gserviceaccount.com",
"sub": "defered#defered.iam.gserviceaccount.com",
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat": 1486824545,
"exp": 1486828145,
"uid": "4",
"claims": {
"premium_account": true
}
}

It looks like the accepted answer found a way to link authentication from Firebase to Rails, but the original question seems to be asking how to link Rails authentication to Firebase (which is what I was trying to do).
To keep your authentication logic in Rails (ex: from Devise) and share it with Firebase, first get a Firebase server key as a .json file from your Service Accounts page in your project's settings.
You'll only need the private_key and client_id from this file, which I recommend storing as environment variables so they're not potentially leaked in source code.
Next, make a Plain ol' Ruby object (PORO) that will take in a User and spit out a JSON Web Token (JWT) that Firebase can understand:
class FirebaseToken
def self.create_from_user(user)
service_account_email = ENV["FIREBASE_CLIENT_EMAIL"]
private_key = OpenSSL::PKey::RSA.new ENV["FIREBASE_PRIVATE_KEY"]
claims = {
isCool: "oh yeah"
}
now_seconds = Time.now.to_i
payload = {
iss: service_account_email,
sub: service_account_email,
aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
iat: now_seconds,
exp: now_seconds + (60*60), # Maximum expiration time is one hour
uid: user.id,
# a hash to pass to the client as JSON
claims: claims
}
JWT.encode payload, private_key, "RS256"
end
end
Now send this JWT to authenticated users through javascript in your application layout:
window.firebaseJWT = "#{FirebaseToken.create_from_user(current_user)}";
In your frontend code, you can now use this token to authenticate users:
firebase
.auth()
.signInWithCustomToken(window.firebaseJWT)
.catch(error => {
console.error(error);
});
Remember to sign them out of firebase when they sign out of your application:
firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
})
.catch(error => {
console.error(error);
});

I found a better way to authenticate, I'm just sending the token that firebase gives you and verifying it on rails with the information I need and that's it.

Check if your secret key is wrapped in double quotes and not single as they contain '\n' escape sequences. An auth/invalid-custom-token error is thrown if the secret key is not as specified in the documentation.

Related

How to create laravel passport refresh token

How to create refreshingtoken using laravel passport.I create access token when login $user->createToken('Laravel')->accessToken.After login Refreshingtoken not working how to create
If your application issues short-lived access tokens, users will need to refresh their access tokens via the refresh token that was provided to them when the access token was issued:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://YOURSITE.COM/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
]);
return $response->json();
This /oauth/token route will return a JSON response containing access_token, refresh_token, and expires_in attributes. The expires_in attribute contains the number of seconds until the access token expires.
Taken from: https://laravel.com/docs/9.x/passport#refreshing-tokens

Newsletter2go ruby API client authorisation with get_token method

Good day!
Can you please give me an example of authentication request with ruby newsletetr2go API client.
I can't figure it out.
I can connect to API using direct requests like RestClient.post "#{link}/oauth/v2/token", credentials, default_header
In credentials I use my username, password and grant_type, converted to json format
In default header I use content_type: 'application/json' and authorization: "Basic #{Base64.strict_encode64(ENV['NEWSLETTER2GO_AUTH_KEY'])}"
And it works fine. But when I try to use newsletter2go get_token method all I receive is "BAD REQUEST" error.
I'm using initializer to configure SwaggerClient like this:
SwaggerClient.configure do |config|
# Configure OAuth2 access token for authorization: OAuth
config.password = ENV['NEWSLETTER2GO_PASSWORD']
config.username = ENV['NEWSLETTER2GO_USERNAME']
config.api_key = ENV['NEWSLETTER2GO_AUTH_KEY']
end
After that I use newsletter2go api method call
SwaggerClient::AuthorizationApi.new.get_token("https://nl2go.com/jwt")
Seems everything is correct, but error "BAD REQUEST" happens all the time.
I followed the instructions, install swagger_client with ruby extentions in github, and newsletter2go methods are now available from my rails environment.
If I grab access_token manually and add it to my initializer, then do some requests like SwaggerClient::ListApi.new.get_lists it gives me a proper response with status 200 and list_ids
But SwaggerClient::AuthorizationApi.new.get_token("https://nl2go.com/jwt") does not work and this is the issue.
Any help would be very appreciated!
I figured out the reason why Newsletter2Go API ruby client does not grab api_key value. For some reason it's hardcoded to setup basic auth token which stands from username and password packed.
Here is code from
module SwaggerClient
class Configuration
# Gets Basic Auth token string
def basic_auth_token
'Basic ' + ["#{username}:#{password}"].pack('m').delete("\r\n")
end
# Returns Auth Settings hash for api client.
def auth_settings
{
'OAuth' =>
{
type: 'oauth2',
in: 'header',
key: 'Authorization',
value: "Bearer #{access_token}"
},
'Basic' =>
{
type: 'basic',
in: 'header',
key: 'Authorization',
value: basic_auth_token
},
}
end

Can I send requests to app with devise without credentials?

I have backend app with devise and a ionic app from which I make requests to backend.
Whenever I send requests, gets me 401 status unauthorized.
In this project I've already doorkeeper to manage authorization, so I don't rly need authorization from devise. Can I somehow make these requests( add something to headers ) always authorized without sending post request with credentials?
You need to identify the user somehow. If you're not using a session, you'll need to generate some kind of access token, which Doorkeeper can do for you. I'm not sure if this is helpful or not, but I set up the following flow recently.
One option when using OAuth2 through a trusted client, e.g. a front-end app you build/distribute yourself, is the Resource Owner Password Credentials Grant. Doorkeeper has a guide in the docs for this with advice on dealing with Devise.
I ended up with something like this in my Doorkeeper initializer. You don't need to authorize the client, because you trust it:
resource_owner_from_credentials do |routes|
request.params[:user] = {:email => request.params[:email], :password => request.params[:password]}
request.env['devise.allow_params_authentication'] = true
request.env['warden'].authenticate!(:scope => :user)
end
skip_authorization do |resource_owner|
true
end
Then you should be able to send a request using the password grant type as follows (also shown in the docs).
RestClient.post 'http://localhost:3000/oauth/token', {grant_type: 'password', email: 'email#gmail.com', password: 'password'}, {:accept => 'application/json'}
You should receive the same JSON back as shown in the docs.
{
"access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
"token_type": "bearer",
"expires_in": 7200
}
Now you have a way of identifying your user. You just attach this token to each request to endpoints protected by doorkeeper_for.
RestClient.get 'http://localhost/api/v1/users', { 'Authorization' => 'Bearer 1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa', :accept => 'application/json'}

Rails: Export data in google spreadsheet

I am new to Rails and I want to export data to Google Spread Sheet from my web application.
I have created an app to get client id and client secret and enabled drive api for that.
I have installed google drive and google api client gem
And I used the code stated here
This code successfully runs, open a new tab for authorization, and displays a code to paste. This is the point where I am stuck. The code that google authorization demands is in my controller code so my user can paste that code in my controller. I know its quiet stupid thing to ask but I am not finding a way to automatically get the code from api to further execution as we usually do in our facebook oauth applications. So can you guide me how to do it? The code looks like
def export_spred_sheet
require 'rubygems'
require 'google/api_client'
require 'launchy'
# Get your credentials from the console
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Create a new API client & load the Google Drive API
client = Google::APIClient.new
drive = client.discovered_api('drive', 'v2')
# Request authorization
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.scope = OAUTH_SCOPE
client.authorization.redirect_uri = REDIRECT_URI
uri = client.authorization.authorization_uri
Launchy.open(uri)
# Exchange authorization code for access token
$stdout.write "Enter authorization code: "
client.authorization.code = gets.chomp
client.authorization.fetch_access_token!
# Insert a file
file = drive.files.insert.request_schema.new({
'title' => 'My document',
'description' => 'A test document',
'mimeType' => 'text/plain'
})
media = Google::APIClient::UploadIO.new('document.txt', 'text/plain')
result = client.execute(
:api_method => drive.files.insert,
:body_object => file,
:media => media,
:parameters => {
'uploadType' => 'multipart',
'alt' => 'json'})
# Pretty print the API result
jj result.data.to_hash
end
Or is there any other way to do the task If I am on wrong track?
I was also fighting against this in one of my project and finally I found soulution as follows:
Instead of using client ID and client secrete you can use P12 key generated in google developer console under service account for authentication. In this case you won't need to paste any code in controller.
To generate p12 key
go to Google developer console
then -> "APIs & Auth" -> "Credentials"
Create New Client ID of type 'Service Account'
Once new client Id generated. Under Service Account section you will find a button to 'generate new P12 key'. On click it will generate a p12 key. Download it and store it securely in your app and use it for authentication.
And use following code snippet to fetch access token.
key_file = p12_key_file_path
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, 'notasecret')
client = Google::APIClient.new application_name: "abcd", application_version: "1.0.0"
SERVICE_ACCOUNT_ID (used in following code snippet) will be Email address generated under this "Service Account" section in google developer console.
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/analytics',
:issuer => SERVICE_ACCOUNT_ID,
:access_type => 'offline',
:signing_key => key
)
client.authorization.fetch_access_token!
client
I hope it will help you.

Rails Google Client API - unable to exchange a refresh token for access token

After struggling with some SSL issues on my machine, I'm still trying to access a user's Blogger account through the Google Ruby Client API. I'm using the following:
Rails 3.2.3
Ruby 1.9.3
oauth2 (0.8.0)
omniauth (1.1.1)
omniauth-google-oauth2 (0.1.13)
google-api-client (0.4.6)
I can successfully authenticate users and access their blogs through the Google API at the time of authentication. When a user logs in, I store the access_token and refresh_token I receive from Google. and everything works great until the access_token expires. I'm trying to build the functionality that exchanges the refresh_token for a new access_token, but keep coming up against walls. Using the client documentation as an example, this is the code I'm using:
client = Google::APIClient.new
token_pair = auth.oauth_token # access_token and refresh_token received during authentication
# Load the access token if it's available
if token_pair
client.authorization.update_token!(token_pair.to_hash)
end
# Update access token if expired
if client.authorization.refresh_token && client.authorization.expired?
client.authorization.fetch_access_token!
end
blogger = client.discovered_api('blogger', 'v3')
result = client.execute(
api_method: blogger.blogs.list_by_user,
parameters: {'userId' => "self", 'fields' => 'items(description,id,name,url)'},
headers: {'Content-Type' => 'application/json'})
This code works perfectly while the access_token is valid. As soon as it expires though, I'm seeing 2 problems:
Even though I know the token is expired (I've checked expires_at value in the database), client.authorization.expired? returns false -- is there a different way I can check the expiration of the token besides using the value in the database?
When I force the execution of client.authorization.fetch_access_token! I get an invalid_request error.
Can someone please let me know how I can exchange a refresh_token for a new access_token using the client API? Even if you know how to do it in another language, that would be a big help as I can then try to Rubyfy it. Thanks!!
You may have already found this, but you can read through the whole process here at google: https://developers.google.com/accounts/docs/OAuth2WebServer
The omniauth-google-oauth2 strategy already takes care of setting the access_type and approval_prompt so getting a refresh token is just a matter of posting to https://accounts.google.com/o/oauth2/token with grant_type=request_token
Here is roughly the code I use:
def refresh_token
data = {
:client_id => GOOGLE_KEY,
:client_secret => GOOGLE_SECRET,
:refresh_token => REFRESH_TOKEN,
:grant_type => "refresh_token"
}
#response = ActiveSupport::JSON.decode(RestClient.post "https://accounts.google.com/o/oauth2/token", data)
if #response["access_token"].present?
# Save your token
else
# No Token
end
rescue RestClient::BadRequest => e
# Bad request
rescue
# Something else bad happened
end
Since you are using the Ruby Google API Client, why not use it to exchange the refresh token as well? The Ruby API does pretty much the same thing internally, which #brimil01 has said in his answer.
This is how I use the Ruby API to exchange my refresh token for a new access token.
def self.exchange_refresh_token( refresh_token )
client = Google::APIClient.new
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.grant_type = 'refresh_token'
client.authorization.refresh_token = refresh_token
client.authorization.fetch_access_token!
client.authorization
end
And according to this issue here, it is recommended not to use the expired? method to check if an access token has expired.
Basically, don't call the expired? method. There are essentially zero
scenarios where that's a good idea. It simply won't give you reliable
expiration information. It's more of a hint than a real expiration
timestamp, and the token server may decide to honor an expired token
anyways in certain somewhat theoretical, but important, circumstances.
If you do get an invalid grant error, always refresh your access token
and retry once. If you're still getting an error, raise the error.
Here is what I do.
# Retrieved stored credentials for the provided user email address.
#
# #param [String] email_address
# User's email address.
# #return [Signet::OAuth2::Client]
# Stored OAuth 2.0 credentials if found, nil otherwise.
def self.get_stored_credentials(email_address)
hash = Thread.current['google_access_token']
return nil if hash.blank?
hash[email_address]
end
##
# Store OAuth 2.0 credentials in the application's database.
#
# #param [String] user_id
# User's ID.
# #param [Signet::OAuth2::Client] credentials
# OAuth 2.0 credentials to store.
def self.store_credentials(email_address, credentials)
Thread.current['google_access_token'] ||= {}
Thread.current['google_access_token'][email_address] = credentials
end
def self.credentials_expired?( credentials )
client = Google::APIClient.new
client.authorization = credentials
oauth2 = client.discovered_api('oauth2', 'v2')
result = client.execute!(:api_method => oauth2.userinfo.get)
(result.status != 200)
end
# #return [Signet::OAuth2::Client]
# OAuth 2.0 credentials containing an access and refresh token.
def self.get_credentials
email_address = ''
# Check if a valid access_token is already available.
credentials = get_stored_credentials( email_address )
# If not available, exchange the refresh_token to obtain a new access_token.
if credentials.blank?
credentials = exchange_refresh_token(REFRESH_TOKEN)
store_credentials(email_address, credentials)
else
are_credentials_expired = credentials_expired?(credentials)
if are_credentials_expired
credentials = exchange_refresh_token(REFRESH_TOKEN)
store_credentials(email_address, credentials)
end
end
credentials
end
I fixed it with simple code below.
def refesh_auth_tooken(refresh_token)
client = Google::APIClient.new
puts "REFESH TOOKEN"
client.authorization = client_secrets
client.authorization.refresh_token = refresh_token
#puts YAML::dump(client.authorization)
client.authorization.fetch_access_token!
return client.authorization
end

Resources