Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Coinbase OAuth With Firebase - Build Bitcoin Apps

Episode 77 written by Jeff Delaney
full courses and content on fireship.io

Bitcoin is the hottest topic in the tech world as we approach 2018. Although it’s price is obviously a bubble, blockchain technology is a game-changer and presents a great opportunity for developers who want to build apps around crypto currencies. In this lesson, I will give you a foundation for building Bitcoin apps by integrating Firebase Auth with the Coinbase OAuth2 API.

This tutorial will teach you two main concepts.

  1. Coinbase OAuth2 Integration with Firebase
  2. Allow users to securely retrieve their Bitcoin wallets (or Ethereum and Litecoin)

Although I use Angular for the frontend, all of the heavy lifting occurs in the backend NodeJS Cloud Function environment, so all the backend code can be used for any client side framework.

Logging in as a Firebase user with Coinbase connect OAuth

Full source code for the Firebase Coinbase.

Are you Bitcoin rich?

Consider donating some bitcoin to AngularFirebase to keep the content free and frequent.

Donate bitcoins to angular firebase

AngularFirebase Bitcoin Address: 13pEjpLf3taeezGkLpn2CMi2YNxaBF7rVZ

Initial Setup

This lesson has quite a few moving parts, so make sure to follow each step carefully.

Coinbase App Setup

Signup for Coinbase and generate a new app. The app contains the your API keys and allows you to configure an OAuth redirect URI. For this demo, the redirect URI is localhost:4200, which is the default port for Angular.

setting up your coinbase oauth app

Angular CLI App

The Angular app is nothing fancy. We just create a basic CLI app with routing:

ng new bitcoinApp --routing
cd bitcoinApp

Then generate a few resources that we will build out later in this lesson.

ng g component user-details
ng g component auth-redirect
ng g service auth -m app

After that, you will need to install AngularFire by following the instructions on the official repo. The app will also import Angular’s HttpClient. Your app module should look something like this:

// ... omitted default imports
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';

import { HttpClientModule } from '@angular/common/http';
import { AuthService } from './auth.service';
import { AuthRedirectComponent } from './auth-redirect/auth-redirect.component';
import { UserDetailsComponent } from './user-details/user-details.component'

@NgModule({
declarations: [
AppComponent,
AuthRedirectComponent,
UserDetailsComponent
],
imports: [
BrowserModule,
AppRoutingModule,
AngularFireModule.initializeApp(yourFirebaseConfig),
AngularFireAuthModule,
HttpClientModule
],
providers: [AuthService],
bootstrap: [AppComponent]
})
export class AppModule { }

Cloud Functions Setup

We’re going to make heavy use of async functions, which provide syntactic sugar for Promises. TypeScript must be used for this code to work because Firebase uses Node v6. If migrating an existing project, you can follow the TypesScript Cloud Functions guide.

npm install firebase-tools -g
firebase init functions
cd functions

Rather then use Node’s built-in HTTP client, I am replacing it with Axios, which is Promise-based. It is going to make our life way easier when we start dealing async functions. We will also need crypto and cors installed as well.

Also install the types for these packages to get the IDE awesomeness of TypeScript.

npm install axios --save
npm install crypto cors --save
npm install @types/{cors,axios} --save-dev

Now, we need Coinbase API credentials saved to the functions environment.

firebase functions:config:set coinbase.id="YOUR ID" coinbase.secret="YOUR SECRET"

Custom OAuth providers like Coinbase must mint their own Firebase Auth Token, so it’s necessary to use the our full service account, rather than the default functions admin config.

Adding your service account to mint custom auth tokens

Download the service account and save it inside the functions directory.

Here’s what all of our default imports look like in index.ts.

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

const serviceAccount = require('../firestarter-96e46-firebase-adminsdk-4n0tv-3a219ba7ec.json')

admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://firestarter-96e46.firebaseio.com'
});

import * as crypto from 'crypto';
import * as qs from 'querystring';
import axios from 'axios';

import * as CORS from 'cors';
const cors = CORS({ origin: true });

const redirect_uri = 'http://<angularApp>/redirect';

const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG);
const client_id = firebaseConfig.coinbase.id;
const client_secret = firebaseConfig.coinbase.secret;

const defaultParams = {
client_id,
client_secret,
redirect_uri
}

The service account holds sensitive API keys - make sure to keep it out of any clientside code and add it to your .gitignore file.

Coinbase OAuth with Firebase

We have two Firebase Cloud Functions that operate as the middle-men between Angular and Coinbase. Here’s a breakdown of how it works step-by-step:

  1. Angular creates a popup that points to the redirect Cloud Function.
  2. Cloud Function redirects to Coinbase with our app credentials.
  3. User logs into their Coinbase account, Coinbase redirects to Angular with an auth code.
  4. Angular sends the auth code to the token Cloud Function
  5. The Cloud Function mints a custom user and sends it in the response.
  6. AngularFire authenticates the user in our app.

Piece of cake.

You’re going to see a few different URLs being thrown around, so let me explain those for you now.

  1. angularApp - Angular, or localhost:4200 locally
  2. cloudFunction - Cloud Function Endpoint, or localhost:5000 locally
  3. coinbase.com/oauth - Coinbase OAuth.
  4. api.coinbase.com - Main Coinbase API.

Redirect Cloud Function

The redirect function is the easy one. It’s just serves as a proxy for our API credentials and securely directs the user to Coinbase to log into their account.

export const redirect = functions.https.onRequest((req, res) => {
const base = 'https://www.coinbase.com/oauth/authorize?';

const queryParams = {
...defaultParams,
response_type: 'code',
scope: 'wallet:accounts:read',
state: crypto.randomBytes(20).toString('hex')
}
const endpoint = base + qs.stringify( queryParams )

res.redirect(endpoint);
});

Token Cloud Function

The token Cloud Function receives the auth code form Coinbase, which is then sent back to Coinbase from the server to generate an access token and refresh token - in other words, the user is logged in as a Coinbase user on our server.

We use the token to get their user ID from the Coinbase API, then mint a custom auth token for Firebase. This token is passed back to Angular where it can be used to log the user into our firebase-powered app.

// Use the code returned from Coinbase to mint a custom auth token in Firebase
export const token = functions.https.onRequest((req, res) => {
cors( req, res, () => {

return mintAuthToken(req)
.then(authToken => res.json({ authToken }))
.catch(err => console.log(err))

});
});

And now for our first async functions. Let’s describe the mintAuthToken step-by-step.

  1. Use the code to authenticate the user.
  2. Get the user account details from coinbase (including the user ID).
  3. Generate a custom UID that looks like coinbase:coinbase-id-xzy and mint it.
  4. Save the coinbase tokens to the Realtime DB.

savning coinbase tokens to firebase

Note about step 4. Coinbase uses refresh tokens, which are used to regenerate a valid access token (they expire every 2 hours). After the user authenticates, you will want to save these in the database to make API calls on the user’s behalf. Make sure to block read/write access to coinbase tokens in your database rules and only access them via the backend.

async function mintAuthToken(req): Promise<string> {
const base = 'https://api.coinbase.com/oauth/token?'

const queryParams = {
...defaultParams,
grant_type: 'authorization_code',
code: req.query.code
}


const endpoint = base + qs.stringify( queryParams )


const login = await axios.post(endpoint);
const accessToken = login.data.access_token
const refreshToken = login.data.refresh_token

const user = await getCoinbaseUser(accessToken)
const uid = 'coinbase:' + user.id

const authToken = await admin.auth().createCustomToken(uid);

await admin.database().ref(`coinbaseTokens/${uid}`).update({ accessToken, refreshToken })

return authToken
}

// Retrieve the user account data from Coinbase
async function getCoinbaseUser(accessToken: any): Promise<any> {
const userUrl = 'https://api.coinbase.com/v2/user';

const user = await axios.get(userUrl, { headers: { 'Authorization': `Bearer ${accessToken}` } });

return user.data.data
}

Wallet Cloud Function

What good would Coinbase auth be without the ability to make requests to the API to view and send crypto currency payments? Let’s write another function that retrieves a user’s wallet details, including its current bitcoin balance.

export const wallet = functions.https.onRequest((req, res) => {
cors( req, res, () => {

return getWallet(req)
.then(wallets => res.json(wallets))
.catch(err => console.log(err))

});
});

Again, let’s break this down step-by-step. This looks like a large amount of code for a single API request, but it can be reused to build functions for other Coinbase API calls.

  1. Validate the authtoken submitted by the user.
  2. Refresh their Coinbase tokens.
  3. Make a GET request the coinbase wallet endpoint.
// Get the user's wallet data from Coinbase
async function getWallet(req): Promise<any> {
const endpoint = 'https://api.coinbase.com/v2/accounts';

const uid = await verifyUser(req)

const accessToken = await updateTokens(uid)
const accounts = await axios.get(endpoint, { headers: { 'Authorization': `Bearer ${accessToken}` } })

return accounts.data.data
}

// Validate the Firebase auth header to authenticate the user
async function verifyUser(req): Promise<string> {
let authToken = req.headers.authorization as string
authToken = authToken.split('Bearer ')[1]

const verifiedToken = await admin.auth().verifyIdToken(authToken)

return verifiedToken.uid
}


// Used to update tokens for an authenticated user.
async function updateTokens(uid: string): Promise<string> {
const base = 'https://api.coinbase.com/oauth/token?';

const oldRefreshToken = await admin.database()
.ref(`coinbaseTokens/${uid}`)
.once('value')
.then(data => data.val().refreshToken)

const queryParams = {
...defaultParams,
refresh_token: oldRefreshToken,
grant_type: 'refresh_token'
}


const endpoint = base + qs.stringify( queryParams )

const response = await axios.post(endpoint)

const accessToken = response.data.access_token
const refreshToken = response.data.refresh_token

await admin.database().ref(`coinbaseTokens/${uid}`).update({ accessToken, refreshToken })

return accessToken
}

Frontend Web App

Now let’s finish up the frontend.

Auth Service

Let’s start by building a basic auth service.

I am interacting with the window object directly for the sake of simplicity, but it is only available in browser apps. If building for mobile, you will need to use a different strategy.

import { Injectable } from '@angular/core';
import { AngularFireAuth } from 'angularfire2/auth';

import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthService {

user: Observable<firebase.User>;

constructor(private afAuth: AngularFireAuth) {
this.user = this.afAuth.authState
}

// Login popup window
login() {
const popup = window.open('http://<cloudFunction>/redirect', '_blank', 'height=700,width=800')
}


// Signin with a custom token from
customSignIn(token) {
return this.afAuth.auth.signInWithCustomToken(token).then(() => window.close() )
}

// Used for outbound requests
getIdToken() {
return this.afAuth.auth.currentUser.getIdToken()
}

signout() {
this.afAuth.auth.signOut()
}

}

User Details Component

The user login button just

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';
import { HttpClient } from '@angular/common/http';

@Component({
selector: 'user-details',
templateUrl: './user-details.component.html',
styleUrls: ['./user-details.component.sass']
})
export class UserDetailsComponent {

wallets;

constructor(public auth: AuthService, private http: HttpClient) { }

getWallet() {
this.wallets = this.auth.getIdToken().then(authToken => {
const endpoint = 'http://<cloudFunction>/wallet';
const headers = {'Authorization': 'Bearer ' + authToken }

return this.http.get(endpoint, { headers }).toPromise()
})
}


}

The simply unwraps the user observable. If defined, it shows the UID and gives the user the ability to retrieve their bitcoin wallet details. Otherwise, it just shows the Login with Coinbase button.

<div *ngIf="auth.user | async as user; else login">
<h3>You are logged into Firebase via Coinbase! UID: {{ user.uid }} </h3>
<button (click)="getWallet()">Get Bitcoin Wallet</button>
<button (click)="auth.signout()">Signout</button>

<div *ngFor="let wallet of wallets | async">
<h1>{{ wallet.name }} - {{ wallet.id }}</h1>

<h3>Current Balance: {{ wallet.balance.amount }} {{wallet.balance.currency }}</h3>

</div>
</div>



<ng-template #login>
<button (click)="auth.login()">Login with Coinbase</button>
</ng-template>

Auth Redirect

The auth redirect page is special because it should only be navigated to by Coinbase with a code query parameter. The component will parse the query param, then make an http request to the Cloud Function we created earlier.

The function should respond with the custom auth token, which we can pass off to AngularFire2 - and we’re logged in!

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { AuthService } from '../auth.service';
import { switchMap, tap } from 'rxjs/operators';
import { fromPromise } from 'rxjs/observable/fromPromise';

@Component({
selector: 'auth-redirect',
templateUrl: './auth-redirect.component.html',
styleUrls: ['./auth-redirect.component.sass']
})
export class AuthRedirectComponent implements OnInit {

constructor(private http: HttpClient, private route: ActivatedRoute, private auth: AuthService) { }

ngOnInit() {
const code = this.route.snapshot.queryParamMap.get('code')

if (code) {
const url = `http://<cloudFunction>/token?code=${code}`;

this.http.post<any>(url, {}).pipe(
switchMap(res => fromPromise( this.auth.customSignIn(res.authToken) ))
)

}
}

}

Up Next

Now that we have a user base full or bitcoin millionaires, we need to give them some more ways to send, receive, and manage their crypto currencies. The next lesson will expand on this foundation with more advanced Coinbase integrations. Let me know if you have any questions in the comments or reach out on Slack.