Skip to content

Commit 8a89ba3

Browse files
Merge pull request #746 from firebase/okta-sample
Okta authentication sample
2 parents 3ed989f + b366196 commit 8a89ba3

File tree

9 files changed

+672
-1
lines changed

9 files changed

+672
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Only users who pass a valid Firebase ID token as a Bearer token in the `Authoriz
172172
Checking the ID token is done with an ExpressJs middleware that also passes the decoded ID token in the Express request object.
173173
Uses an HTTP trigger.
174174

175-
### Authorize with [LinkedIn](/linkedin-auth), [Spotify](spotify-auth), [Instagram](/instagram-auth), [LINE](/line-auth) or [Basic Auth](/username-password-auth)
175+
### Authorize with [Okta](/okta-auth), [LinkedIn](/linkedin-auth), [Spotify](spotify-auth), [Instagram](/instagram-auth), [LINE](/line-auth) or [Basic Auth](/username-password-auth)
176176

177177
Demonstrates how to authorize with a 3rd party sign-in mechanism, create a Firebase custom auth token, update the user's profile and authorize Firebase.
178178
Uses an HTTP trigger.

okta-auth/.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
service_account_private_key.json
2+
.runtimeconfig.json
3+
ui-debug.log
4+
public/okta-config.js
5+
functions/.env

okta-auth/README.md

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Authenticate with Firebase using Okta
2+
3+
Sample app that demonstrates how to authenticate with Firebase using Okta.
4+
5+
## Overview
6+
7+
This sample has two parts:
8+
9+
- A Node.js backend that “exchanges” Okta access tokens for Firebase custom
10+
authentication tokens. The backend is intended to be deployed as a Cloud
11+
Function, but because its just an Express.js app, you can also run on it on
12+
your own infrastructure.
13+
- A web frontend that signs users in with Okta, gets a Firebase custom
14+
authentication token from your backend, and authenticates with Firebase using
15+
the custom token.
16+
17+
## Setup
18+
19+
Before you try the demo with the Firebase emulator suite or deploy it to
20+
Firebase Hosting and Cloud Functions, set up your Okta and Firebase projects,
21+
and install the Firebase CLI tool:
22+
23+
1. On the [Okta Developer site](https://developer.okta.com/):
24+
25+
1. Sign in or sign up.
26+
2. Take note of your **Org URL** (top-right of the dashboard) for later.
27+
3. Create a user with a password in your Okta project. (This demo doesn't
28+
have a sign-up flow.)
29+
4. On the Applications page, add a Single-Page App:
30+
1. Set the **Base URIs** to `http://localhost:5000`.
31+
2. Set the **Login redirect URIs** to `http://localhost:5000`.
32+
3. Enable the **Authorization Code** grant type.
33+
4. Click **Done**. Take note of the app's **Client ID** for later.
34+
5. In **API > Trusted Origins**, confirm that `http://localhost:5000` is
35+
listed, with **CORS** and **Redirect** enabled.
36+
37+
2. In the [Firebase console](https://console.firebase.google.com/):
38+
39+
1. Create a new Firebase project. Take note of your **project ID** for
40+
later.
41+
2. On the Project Overview page, add a new web app. Be sure **Also set up
42+
Firebase Hosting for this app** is selected.
43+
3. If you plan to try the demo in the emulator, [generate and download a
44+
service account key][svcacct].
45+
46+
3. If you don't already have a Node.js 10 (or newer) environment,
47+
[install Node.js](https://nodejs.org/).
48+
49+
4. If you haven't already installed the Firebase CLI tool, do it now:
50+
51+
```
52+
$ npm install --global firebase-tools
53+
```
54+
55+
[svcacct]: https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk
56+
57+
## Try the demo with the Firebase emulator suite
58+
59+
1. Make sure the Firebase CLI tool is set to use your Firebase project:
60+
61+
```
62+
$ cd functions-samples/okta-auth
63+
okta-auth$ firebase login
64+
okta-auth$ firebase use <YOUR_FIREBASE_PROJECT_ID>
65+
```
66+
67+
2. Run `setup.js` from the Firebase project directory:
68+
69+
```
70+
okta-auth$ node setup.js
71+
```
72+
73+
The script will prompt you for some of your Okta and Firebase configuration
74+
values and create configurations files from them. The script won't
75+
overwrite existing files.
76+
77+
3. Start the emulators:
78+
79+
```
80+
okta-auth$ firebase emulators:start
81+
```
82+
83+
4. Open the web app: [`http://localhost:5000`](http://localhost:5000).
84+
85+
## Deploy the demo to Firebase Hosting and Cloud Functions
86+
87+
1. [Upgrade your Firebase project to the Blaze (pay as you go) plan](https://console.firebase.google.com/project/_/overview?purchaseBillingPlan=metered).
88+
The Blaze plan is required to access external services (Okta) from Cloud
89+
Functions.
90+
91+
2. In the Google Cloud console:
92+
93+
1. [Enable the IAM Service Account Credentials API](https://console.cloud.google.com/apis/api/iamcredentials.googleapis.com/overview?project=_).
94+
2. On the [IAM](https://console.developers.google.com/iam-admin/iam?project=_)
95+
page, edit the account named **App Engine default service account** and
96+
add the **Service Account Token Creator** role.
97+
98+
3. Make sure the Firebase CLI tool is set to use your Firebase project:
99+
100+
```
101+
$ cd functions-samples/okta-auth
102+
okta-auth$ firebase login
103+
okta-auth$ firebase use <YOUR_FIREBASE_PROJECT_ID>
104+
```
105+
106+
4. Optional: If you have configuration files from local testing, delete them:
107+
108+
```
109+
okta-auth$ rm public/okta-config.js ; rm functions/.env ; rm .runtimeconfig.json
110+
```
111+
112+
5. Run `setup.js -d` from the Firebase project directory. The `-d` flag
113+
configures the web app and backend for deployment.
114+
115+
```
116+
okta-auth$ node setup.js -d
117+
```
118+
119+
The script will prompt you for some of your Okta and Firebase configuration
120+
values, create configurations files from them, and set some Cloud Funcions
121+
environment settings. The script won't overwrite existing files or Cloud
122+
Functions environment settings.
123+
124+
6. Deploy the project:
125+
126+
```
127+
okta-auth$ firebase deploy
128+
```
129+
130+
7. Open the web app at: `https://<YOUR_FIREBASE_PROJECT_ID>.web.app`

okta-auth/firebase.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"functions": {
3+
"predeploy": [
4+
"npm --prefix \"$RESOURCE_DIR\" run lint",
5+
"node setup.js -d"
6+
]
7+
},
8+
"hosting": {
9+
"public": "public",
10+
"ignore": [
11+
"firebase.json",
12+
"**/.*",
13+
"**/node_modules/**"
14+
],
15+
"predeploy": [
16+
"node setup.js -d"
17+
]
18+
}
19+
}

okta-auth/functions/.eslintrc.json

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"parserOptions": {
3+
// Required for certain syntax usages
4+
"ecmaVersion": 2017
5+
},
6+
"plugins": [
7+
"promise"
8+
],
9+
"extends": "eslint:recommended",
10+
"rules": {
11+
// Removed rule "disallow the use of console" from recommended eslint rules
12+
"no-console": "off",
13+
14+
// Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
15+
"no-regex-spaces": "off",
16+
17+
// Removed rule "disallow the use of debugger" from recommended eslint rules
18+
"no-debugger": "off",
19+
20+
// Removed rule "disallow unused variables" from recommended eslint rules
21+
"no-unused-vars": "off",
22+
23+
// Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
24+
"no-mixed-spaces-and-tabs": "off",
25+
26+
// Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
27+
"no-undef": "off",
28+
29+
// Warn against template literal placeholder syntax in regular strings
30+
"no-template-curly-in-string": 1,
31+
32+
// Warn if return statements do not either always or never specify values
33+
"consistent-return": 1,
34+
35+
// Warn if no return statements in callbacks of array methods
36+
"array-callback-return": 1,
37+
38+
// Require the use of === and !==
39+
"eqeqeq": 2,
40+
41+
// Disallow the use of alert, confirm, and prompt
42+
"no-alert": 2,
43+
44+
// Disallow the use of arguments.caller or arguments.callee
45+
"no-caller": 2,
46+
47+
// Disallow null comparisons without type-checking operators
48+
"no-eq-null": 2,
49+
50+
// Disallow the use of eval()
51+
"no-eval": 2,
52+
53+
// Warn against extending native types
54+
"no-extend-native": 1,
55+
56+
// Warn against unnecessary calls to .bind()
57+
"no-extra-bind": 1,
58+
59+
// Warn against unnecessary labels
60+
"no-extra-label": 1,
61+
62+
// Disallow leading or trailing decimal points in numeric literals
63+
"no-floating-decimal": 2,
64+
65+
// Warn against shorthand type conversions
66+
"no-implicit-coercion": 1,
67+
68+
// Warn against function declarations and expressions inside loop statements
69+
"no-loop-func": 1,
70+
71+
// Disallow new operators with the Function object
72+
"no-new-func": 2,
73+
74+
// Warn against new operators with the String, Number, and Boolean objects
75+
"no-new-wrappers": 1,
76+
77+
// Disallow throwing literals as exceptions
78+
"no-throw-literal": 2,
79+
80+
// Require using Error objects as Promise rejection reasons
81+
"prefer-promise-reject-errors": 2,
82+
83+
// Enforce “for” loop update clause moving the counter in the right direction
84+
"for-direction": 2,
85+
86+
// Enforce return statements in getters
87+
"getter-return": 2,
88+
89+
// Disallow await inside of loops
90+
"no-await-in-loop": 2,
91+
92+
// Disallow comparing against -0
93+
"no-compare-neg-zero": 2,
94+
95+
// Warn against catch clause parameters from shadowing variables in the outer scope
96+
"no-catch-shadow": 1,
97+
98+
// Disallow identifiers from shadowing restricted names
99+
"no-shadow-restricted-names": 2,
100+
101+
// Enforce return statements in callbacks of array methods
102+
"callback-return": 2,
103+
104+
// Require error handling in callbacks
105+
"handle-callback-err": 2,
106+
107+
// Warn against string concatenation with __dirname and __filename
108+
"no-path-concat": 1,
109+
110+
// Prefer using arrow functions for callbacks
111+
"prefer-arrow-callback": 1,
112+
113+
// Return inside each then() to create readable and reusable Promise chains.
114+
// Forces developers to return console logs and http calls in promises.
115+
"promise/always-return": 2,
116+
117+
//Enforces the use of catch() on un-returned promises
118+
"promise/catch-or-return": 2,
119+
120+
// Warn against nested then() or catch() statements
121+
"promise/no-nesting": 1
122+
}
123+
}

okta-auth/functions/index.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Implements the `/api/firebaseCustomToken` endpoint, which is responsible for
3+
* validating Okta access tokens and minting Firebase custom authentication
4+
* tokens.
5+
*/
6+
const express = require('express');
7+
const app = express();
8+
9+
// For local testing, use GOOGLE_APPLICATION_CREDENTIALS from `.env` instead of
10+
// the value set by the emulator. Generate a .`env` file with `setup.js`, or
11+
// create it manually.
12+
const envCfg = require('dotenv').config();
13+
if (envCfg.parsed && envCfg.parsed.GOOGLE_APPLICATION_CREDENTIALS) {
14+
process.env.GOOGLE_APPLICATION_CREDENTIALS =
15+
envCfg.parsed.GOOGLE_APPLICATION_CREDENTIALS;
16+
}
17+
18+
const functions = require('firebase-functions');
19+
const firebaseAdmin = require('firebase-admin');
20+
const firebaseApp = firebaseAdmin.initializeApp();
21+
22+
const OKTA_ORG_URL = functions.config().okta_auth.org_url
23+
const OktaJwtVerifier = require('@okta/jwt-verifier');
24+
const oktaJwtVerifier = new OktaJwtVerifier({
25+
issuer: `${OKTA_ORG_URL}/oauth2/default`
26+
});
27+
28+
// Update CORS_ORIGIN to the base URL of your web client before deploying or
29+
// using a non-standard emulator configuration.
30+
const CORS_ORIGIN = functions.config().okta_auth.cors_origin ||
31+
'http://localhost:5000';
32+
const cors = require('cors')({ origin: CORS_ORIGIN });
33+
34+
// Middleware to authenticate requests with an Okta access token.
35+
// https://developer.okta.com/docs/guides/protect-your-api/nodeexpress/require-authentication/
36+
const oktaAuth = async (req, res, next) => {
37+
const authHeader = req.headers.authorization || '';
38+
const match = authHeader.match(/Bearer (.+)/);
39+
40+
if (!match) {
41+
res.status(401);
42+
return next('Unauthorized');
43+
}
44+
45+
const accessToken = match[1];
46+
try {
47+
const jwt = await oktaJwtVerifier.verifyAccessToken(
48+
accessToken, 'api://default');
49+
req.jwt = jwt;
50+
return next();
51+
} catch (err) {
52+
console.log(err.message);
53+
res.status(401);
54+
return next('Unauthorized');
55+
}
56+
}
57+
58+
// Get a Firebase custom auth token for the authenticated Okta user.
59+
app.get('/firebaseCustomToken', [cors, oktaAuth], async (req, res) => {
60+
const oktaUid = req.jwt.claims.uid;
61+
try {
62+
const firebaseToken =
63+
await firebaseApp.auth().createCustomToken(oktaUid);
64+
res.send(firebaseToken);
65+
} catch (err) {
66+
console.log(err.message);
67+
res.status(500).send('Error minting token.');
68+
}
69+
});
70+
71+
// Enable CORS pre-flight requests.
72+
app.options('/firebaseCustomToken', cors);
73+
74+
exports.api = functions.https.onRequest(app);

okta-auth/functions/package.json

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "firebase-okta-auth",
3+
"description": "Example of authenticating with Firebase using Okta",
4+
"scripts": {
5+
"lint": "eslint .",
6+
"serve": "firebase emulators:start --only functions",
7+
"shell": "firebase functions:shell",
8+
"start": "npm run shell",
9+
"deploy": "firebase deploy --only functions",
10+
"logs": "firebase functions:log"
11+
},
12+
"engines": {
13+
"node": "10"
14+
},
15+
"dependencies": {
16+
"@okta/jwt-verifier": "^1.0.0",
17+
"@okta/oidc-middleware": "^4.0.1",
18+
"cors": "^2.8.5",
19+
"dotenv": "^8.2.0",
20+
"express": "^4.17.1",
21+
"firebase-admin": "^8.13.0",
22+
"firebase-functions": "^3.6.1"
23+
},
24+
"devDependencies": {
25+
"eslint": "^5.12.0",
26+
"eslint-plugin-promise": "^4.0.1",
27+
"firebase-functions-test": "^0.2.0"
28+
},
29+
"private": true
30+
}

0 commit comments

Comments
 (0)