Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/API-Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ These JS APIs falls into the most common category and almost all apps, component

- [Audio](API/fliplet-audio.md) (`fliplet-audio`)
- [Audio Player](API/fliplet-audio-player.md) (`fliplet-audio-player`)
- [Auth](API/fliplet-auth.md) (`fliplet-auth`)
- [Barcode](API/fliplet-barcode.md) (`fliplet-barcode`)
- [Communicate](API/fliplet-communicate.md) (`fliplet-communicate`)
- [Content](API/fliplet-content.md) (`fliplet-content`)
Expand Down
222 changes: 222 additions & 0 deletions docs/API/fliplet-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
description: Sign users into your Fliplet app with one line of code — email/password or SSO (Google, Microsoft, Apple).
---

# Auth JS APIs

The Fliplet Auth JS APIs sign users into your Fliplet app with their existing Fliplet account. One call opens a popup, handles the authentication round-trip, and resolves with the signed-in user. Email/password and SSO (Google, Microsoft, Apple) are supported out of the box.

Use these APIs to authenticate Fliplet users, read the signed-in user in your app code, sign users out, and get the auth token for custom API requests.

## Before you start

Add the `fliplet-auth` package to your app's dependencies. This exposes `Fliplet.Auth.*` at runtime.

<p class="warning"><code>Fliplet.Auth</code> authenticates <strong>existing Fliplet user accounts</strong>. It does not create new accounts from inside an app — only Fliplet Studio's sign-up page can do that. A user who tries to sign in with an SSO provider (Google, Microsoft, Apple) that isn't linked to any Fliplet account sees an error asking them to sign up at Fliplet Studio first. Direct users to Fliplet Studio to create their account, then back to your app to sign in.</p>

`Fliplet.Auth` is the high-level API for signing users in and out — prefer it for app code. It is distinct from two other APIs that share related concepts:

- **`Fliplet.User`** — low-level primitives (`getAuthToken`, `setAuthToken`, `getCachedSession`). `Fliplet.Auth` uses these internally; you rarely need to call them directly.
- **Data source login component** — an app-level authentication system that uses a data source as the user table. It's a separate system from `Fliplet.Auth` and is appropriate when you want users stored in your app's own data source rather than in Fliplet's user database. See [data source login](components/login.md) for that.

## Methods

### Sign a user in

`Fliplet.Auth.signIn()` opens the unified sign-in popup and resolves with `{ user, token }` when the user successfully signs in.

```js
Fliplet.Auth.signIn().then(function(result) {
// result.user: { id, email, firstName, lastName, userRoleId }
// result.token: auth token string, e.g. "eu--55a54008...-203-8965"
console.log('Signed in as', result.user.email);
}).catch(function(err) {
// surface the error to the user and let them retry
console.error(err.message);
});
```

Pre-fill the email field in the sign-in form by passing `options.email`:

```js
Fliplet.Auth.signIn({ email: 'alice@acme.com' });
```

The promise **rejects with an `Error`** when:

- The popup is blocked by the browser → `Error('Sign-in popup was blocked...')`
- The user closes the popup without completing sign-in → `Error('Sign-in was cancelled.')`
- Sign-in times out after 10 minutes → `Error('Sign-in timed out. Please try again.')`
- The API returns a sign-in error → `Error(<message from API>)`

Always handle rejection in your app code — the examples on this page consistently do so.

### Get the currently signed-in user

`Fliplet.Auth.currentUser()` resolves to the signed-in user, or `null` when no user is signed in.

```js
Fliplet.Auth.currentUser().then(function(user) {
if (!user) {
// not signed in
return;
}

// user shape: { id, email, userRoleId }
console.log(user.email);
});
```

The user object returned by `currentUser()` contains a subset of the fields returned by `signIn()` — specifically the fields that persist locally between sessions: `id`, `email`, `userRoleId`. To access `firstName` and `lastName`, either store them yourself after `signIn()` resolves, or query `v1/user` with `Fliplet.Auth.getToken()`.

### Check whether a user is signed in

`Fliplet.Auth.isSignedIn()` resolves to `true` or `false` — a convenience helper on top of `currentUser()`.

```js
Fliplet.Auth.isSignedIn().then(function(isSignedIn) {
if (isSignedIn) {
// show authenticated UI
}
});
```

### Get the user's auth token

`Fliplet.Auth.getToken()` resolves to the signed-in user's auth token, or `null` when no user is signed in. Use it to authenticate custom API calls as the signed-in user.

```js
Fliplet.Auth.getToken().then(function(token) {
if (!token) {
throw new Error('Not signed in');
}

return fetch('https://api.example.com/my-endpoint', {
headers: { 'Auth-Token': token }
});
});
```

Pair with `Fliplet.API.request()` for calls to the Fliplet API:

```js
Fliplet.Auth.getToken().then(function(token) {
return Fliplet.API.request({
url: 'v1/user',
headers: { 'Auth-Token': token }
});
}).then(function(response) {
console.log(response.user);
});
```

### Sign the user out

`Fliplet.Auth.signOut()` clears locally stored credentials and invalidates the server-side session.

```js
Fliplet.Auth.signOut().then(function() {
// user is signed out; currentUser() now resolves to null
});
```

If the server-side logout request fails (for example, the device is offline), local credentials are still cleared so the user is effectively signed out.

### Listen for sign-in / sign-out events

`Fliplet.Auth.onChange(callback)` subscribes to auth state changes. The callback fires with the new user after a successful sign-in, or with `null` after sign-out completes. Returns an unsubscribe function.

```js
var unsubscribe = Fliplet.Auth.onChange(function(user) {
if (user) {
// user just signed in — refresh the UI as this user
} else {
// user just signed out — reset to logged-out state
}
});

// Stop listening when no longer needed
unsubscribe();
```

Useful when the user signs in or out via a different mechanism — for example, a Fliplet Login component on another screen.

## Examples

### Gate a screen behind sign-in

Prompt the user to sign in if they're not already signed in, then render the screen.

```js
Fliplet.Auth.currentUser().then(function(user) {
if (user) {
return user;
}

return Fliplet.Auth.signIn().then(function(result) {
return result.user;
});
}).then(function(user) {
// render the screen as this user
document.querySelector('.greeting').textContent = 'Welcome, ' + user.email;
}).catch(function(err) {
// user cancelled sign-in or it failed
console.error(err.message);
});
```

### Show the signed-in user's name in the header

```js
Fliplet.Auth.currentUser().then(function(user) {
var greeting = user
? 'Signed in as ' + user.email
: 'Not signed in';

document.querySelector('.profile-greeting').textContent = greeting;
});
```

### Sign-out button

```js
document.querySelector('.sign-out-button').addEventListener('click', function() {
Fliplet.Auth.signOut().then(function() {
location.reload();
});
});
```

### Refresh the UI when auth state changes

```js
Fliplet.Auth.onChange(function(user) {
if (user) {
document.body.classList.add('signed-in');
} else {
document.body.classList.remove('signed-in');
}
});
```

### Make an authenticated API request

```js
Fliplet.Auth.getToken().then(function(token) {
if (!token) {
throw new Error('Not signed in');
}

return Fliplet.API.request({
url: 'v1/organizations',
headers: { 'Auth-Token': token }
});
}).then(function(response) {
console.log('Organizations:', response.organizations);
}).catch(function(err) {
console.error(err);
});
```

[Back to API documentation](../API-Documentation.md)
{: .buttons}
1 change: 1 addition & 0 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
<li><a href="/API-Documentation.html">Overview</a></li>
<li><p>Libraries</p></li>
<li><a href="/API/core/overview.html">Core</a></a></li>
<li><a href="/API/fliplet-auth.html">Auth <span class="label">NEW</span></a></li>
<li><a href="/API/fliplet-communicate.html">Communicate</a></li>
<li><a href="/API/fliplet-datasources.html">Data Sources</a></li>
<li><a href="/API/fliplet-encryption.html">Encryption</a></li>
Expand Down
54 changes: 43 additions & 11 deletions fliplet-login.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require('colors');

const _ = require('lodash');
const url = require('url');

const auth = require('./lib/auth');
const config = require('./lib/config');
Expand All @@ -20,7 +20,16 @@ ${'Press Ctrl+C to exit.'.blue}

const port = 9001;
const baseUrl = `http://localhost:${port}`;
const redirectUrl = `${config.api_url}v1/auth/third-party?redirect=${encodeURIComponent(`${baseUrl}/callback`)}&responseType=code&source=CLI&title=Sign%20in%20to%20Authorize%20the%20Fliplet%20CLI`;
const callbackUrl = `${baseUrl}/callback`;

// New unified sign-in URL — replaces the legacy /v1/auth/third-party flow.
// The page validates the callback URL against an allow-list (which already
// includes http://localhost:9001/callback) and on success navigates the
// browser to <callback>?token=XXX&user=<base64-json>.
const redirectUrl = `${config.api_url}v1/auth/login`
+ `?return=callback`
+ `&callback=${encodeURIComponent(callbackUrl)}`
+ `&source=CLI`;

require('http').createServer(function(req, res) {
if (req.url.match(/login/)) {
Expand All @@ -38,37 +47,60 @@ require('http').createServer(function(req, res) {
}

if (req.url.match(/callback/)) {
const authToken = _.last(req.url.split('auth_token='));
// Parse the token (and optional user payload) from the callback URL.
// The unified contract delivers them as `?token=...&user=<base64>`.
const parsed = url.parse(req.url, true);
const authToken = parsed.query.token;

if (!authToken) {
console.error('No auth token received from the sign-in flow.');
res.writeHead(400);
res.end('Missing token');
return process.exit(1);
}

auth.setUserForToken(authToken).then(function(user) {
organizations.getOrganizationsList().then(function onGetOrganizations(organizations) {
if (!organizations.length) {
return console.error('Your organization has not been found.');
}

if (organizations.length > 1) {
return console.error('You belong to multiple organizations.');
console.error('Your organization has not been found.');
res.writeHead(400);
res.end('No organizations available for this account.');
return process.exit(1);
}

// For users belonging to multiple organizations, default to the
// first one. They can switch later via `fliplet organization <id>`
// (use `fliplet list-organizations` to see all available IDs).
const userOrganization = organizations[0];

config.set('organization', userOrganization);

console.log('----------------------------------------------------------\r\n');
console.log(`You have logged in successfully. Welcome back, ${user.fullName.yellow.underline}!`);
console.log(`Your organization has been set to ${userOrganization.name.green.underline} (#${userOrganization.id}). Your account email is ${user.email.yellow.underline}.`);
console.log(`You can now develop and publish components via the ${'fliplet run'.bgBlack.red} command!
`);

if (organizations.length > 1) {
console.log(`\n${'Note'.yellow}: you belong to ${organizations.length} organizations. To switch, run ${'fliplet list-organizations'.bgBlack.red} to see all of them and ${'fliplet organization <id>'.bgBlack.red} to change.`);
}

console.log(`\nYou can now develop and publish components via the ${'fliplet run'.bgBlack.red} command!\n`);

res.writeHead(302, {
'Location': `${baseUrl}/success?${Date.now()}`
});

return res.end();
}).catch(function(err) {
console.error('Failed to fetch organizations:', err);
res.writeHead(500);
res.end('Failed to fetch organizations');
return process.exit(1);
});
}).catch(function(err) {
console.error(err);
process.exit();
res.writeHead(500);
res.end('Authentication failed');
return process.exit(1);
});
}
}).listen(9001);
Expand Down
8 changes: 6 additions & 2 deletions lib/organizations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ const request = require('request');
const config = require('./config');

var organizationsList;
var user = config.data.user || {};
var auth_token = user.auth_token;

function getOrganizationsListFromServer() {
return new Promise(function(resolve, reject) {
// Read the auth token lazily — sign-in may have happened after
// this module was loaded, in which case the cached user object
// from module-init time would be stale.
var user = config.data.user || {};
var auth_token = user.auth_token;

if (!auth_token) {
return reject('You must login first with: fliplet login');
}
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.