Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions docs-devsite/_toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ toc:
path: /docs/reference/js/auth.emulatorconfig.md
- title: FacebookAuthProvider
path: /docs/reference/js/auth.facebookauthprovider.md
- title: FirebaseToken
path: /docs/reference/js/auth.firebasetoken.md
- title: GithubAuthProvider
path: /docs/reference/js/auth.githubauthprovider.md
- title: GoogleAuthProvider
Expand Down
13 changes: 13 additions & 0 deletions docs-devsite/auth.auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Auth
| [config](./auth.auth.md#authconfig) | [Config](./auth.config.md#config_interface) | The [Config](./auth.config.md#config_interface) used to initialize this instance. |
| [currentUser](./auth.auth.md#authcurrentuser) | [User](./auth.user.md#user_interface) \| null | The currently signed-in user (or null). |
| [emulatorConfig](./auth.auth.md#authemulatorconfig) | [EmulatorConfig](./auth.emulatorconfig.md#emulatorconfig_interface) \| null | The current emulator configuration (or null). |
| [firebaseToken](./auth.auth.md#authfirebasetoken) | [FirebaseToken](./auth.firebasetoken.md#firebasetoken_interface) \| null | The token response initialized via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. |
| [languageCode](./auth.auth.md#authlanguagecode) | string \| null | The [Auth](./auth.auth.md#auth_interface) instance's language code. |
| [name](./auth.auth.md#authname) | string | The name of the app associated with the <code>Auth</code> service instance. |
| [settings](./auth.auth.md#authsettings) | [AuthSettings](./auth.authsettings.md#authsettings_interface) | The [Auth](./auth.auth.md#auth_interface) instance's settings. |
Expand Down Expand Up @@ -87,6 +88,18 @@ The current emulator configuration (or null).
readonly emulatorConfig: EmulatorConfig | null;
```

## Auth.firebaseToken

The token response initialized via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint.

This field is only supported for [Auth](./auth.auth.md#auth_interface) instance that have defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface)<!-- -->.

<b>Signature:</b>

```typescript
readonly firebaseToken: FirebaseToken | null;
```

## Auth.languageCode

The [Auth](./auth.auth.md#auth_interface) instance's language code.
Expand Down
40 changes: 40 additions & 0 deletions docs-devsite/auth.firebasetoken.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Project: /docs/reference/js/_project.yaml
Book: /docs/reference/_book.yaml
page_type: reference

{% comment %}
DO NOT EDIT THIS FILE!
This is generated by the JS SDK team, and any local changes will be
overwritten. Changes should be made in the source code at
https://github.com/firebase/firebase-js-sdk
{% endcomment %}

# FirebaseToken interface
<b>Signature:</b>

```typescript
export interface FirebaseToken
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [expirationTime](./auth.firebasetoken.md#firebasetokenexpirationtime) | number | |
| [token](./auth.firebasetoken.md#firebasetokentoken) | string | |

## FirebaseToken.expirationTime

<b>Signature:</b>

```typescript
readonly expirationTime: number;
```

## FirebaseToken.token

<b>Signature:</b>

```typescript
readonly token: string;
```
1 change: 1 addition & 0 deletions docs-devsite/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Firebase Authentication
| [ConfirmationResult](./auth.confirmationresult.md#confirmationresult_interface) | A result from a phone number sign-in, link, or reauthenticate call. |
| [Dependencies](./auth.dependencies.md#dependencies_interface) | The dependencies that can be used to initialize an [Auth](./auth.auth.md#auth_interface) instance. |
| [EmulatorConfig](./auth.emulatorconfig.md#emulatorconfig_interface) | Configuration of Firebase Authentication Emulator. |
| [FirebaseToken](./auth.firebasetoken.md#firebasetoken_interface) | |
| [IdTokenResult](./auth.idtokenresult.md#idtokenresult_interface) | Interface representing ID token result obtained from [User.getIdTokenResult()](./auth.user.md#usergetidtokenresult)<!-- -->. |
| [MultiFactorAssertion](./auth.multifactorassertion.md#multifactorassertion_interface) | The base class for asserting ownership of a second factor. |
| [MultiFactorError](./auth.multifactorerror.md#multifactorerror_interface) | The error thrown when the user needs to provide a second factor to sign in successfully. |
Expand Down
4 changes: 3 additions & 1 deletion packages/auth/src/api/authentication/exchange_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ export interface ExchangeTokenRequest {
}

export interface ExchangeTokenResponse {
// The firebase access token (JWT signed by Firebase Auth).
accessToken: string;
expiresIn?: string;
// The time when the access token expires.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is this in seconds?

Copy link
Contributor Author

@mansisampat mansisampat May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, The expiresIn is in seconds.

expiresIn: number;
}

export async function exchangeToken(
Expand Down
12 changes: 11 additions & 1 deletion packages/auth/src/core/auth/auth_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import {
NextFn,
Unsubscribe,
PasswordValidationStatus,
TenantConfig
TenantConfig,
FirebaseToken
} from '../../model/public_types';
import {
createSubscribe,
Expand Down Expand Up @@ -100,6 +101,7 @@ export const enum DefaultConfig {
export class AuthImpl implements AuthInternal, _FirebaseService {
currentUser: User | null = null;
emulatorConfig: EmulatorConfig | null = null;
firebaseToken: FirebaseToken | null = null;
private operations = Promise.resolve();
private persistenceManager?: PersistenceUserManager;
private redirectPersistenceManager?: PersistenceUserManager;
Expand Down Expand Up @@ -455,6 +457,14 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
});
}

async _updateFirebaseToken(
firebaseToken: FirebaseToken | null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to accept null here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah i just saw your new changes in https://github.com/firebase/firebase-js-sdk/pull/9061/files. If this.firebaseToken can be null, then this makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. We are updating FirebaseToken with null after token expiry.

): Promise<void> {
if (firebaseToken) {
this.firebaseToken = firebaseToken;
}
}

async signOut(): Promise<void> {
if (_isFirebaseServerApp(this.app)) {
return Promise.reject(
Expand Down
10 changes: 9 additions & 1 deletion packages/auth/src/core/strategies/exchange_token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
testAuth,
TestAuth
} from '../../../test/helpers/mock_auth';
import * as sinon from 'sinon';
import * as mockFetch from '../../../test/helpers/mock_fetch';
import { HttpHeader, RegionalEndpoint } from '../../api';
import { exchangeToken } from './exhange_token';
Expand All @@ -35,19 +36,23 @@ use(chaiAsPromised);
describe('core/strategies/exchangeToken', () => {
let auth: TestAuth;
let regionalAuth: TestAuth;
let now: number;

beforeEach(async () => {
auth = await testAuth();
regionalAuth = await regionalTestAuth();
mockFetch.setUp();
now = Date.now();
sinon.stub(Date, 'now').returns(now);
});
afterEach(mockFetch.tearDown);
afterEach(() => sinon.restore());

it('should return a valid access token for Regional Auth', async () => {
const mock = mockRegionalEndpointWithParent(
RegionalEndpoint.EXCHANGE_TOKEN,
'projects/test-project-id/locations/us/tenants/tenant-1/idpConfigs/idp-config',
{ accessToken: 'outbound-token', expiresIn: '1000' }
{ accessToken: 'outbound-token', expiresIn: 10 }
);

const accessToken = await exchangeToken(
Expand All @@ -65,6 +70,8 @@ describe('core/strategies/exchangeToken', () => {
expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq(
'application/json'
);
expect(regionalAuth.firebaseToken?.token).to.equal('outbound-token');
expect(regionalAuth.firebaseToken?.expirationTime).to.equal(now + 10_000);
});

it('throws exception for default Auth', async () => {
Expand Down Expand Up @@ -106,5 +113,6 @@ describe('core/strategies/exchangeToken', () => {
expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq(
'application/json'
);
expect(regionalAuth.firebaseToken).is.null;
});
});
7 changes: 6 additions & 1 deletion packages/auth/src/core/strategies/exhange_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ export async function exchangeToken(
parent: buildParent(auth, idpConfigId),
token: customToken
});
// TODO(sammansi): Write token to the Auth object passed.
if (token) {
await authInternal._updateFirebaseToken({
token: token.accessToken,
expirationTime: Date.now() + token.expiresIn * 1000
});
}
return token.accessToken;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/model/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
AuthSettings,
Config,
EmulatorConfig,
FirebaseToken,
PasswordPolicy,
PasswordValidationStatus,
PopupRedirectResolver,
Expand Down Expand Up @@ -75,6 +76,7 @@ export interface AuthInternal extends Auth {
_initializationPromise: Promise<void> | null;
_persistenceManagerAvailable: Promise<void>;
_updateCurrentUser(user: UserInternal | null): Promise<void>;
_updateFirebaseToken(firebaseToken: FirebaseToken | null): Promise<void>;

_onStorageEvent(): void;

Expand Down
15 changes: 15 additions & 0 deletions packages/auth/src/model/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ export interface Auth {
* {@link @firebase/app#FirebaseServerApp}.
*/
signOut(): Promise<void>;
/**
* The token response initialized via {@link exchangeToken} endpoint.
*
* @remarks
* This field is only supported for {@link Auth} instance that have defined
* {@link TenantConfig}.
*/
readonly firebaseToken: FirebaseToken | null;
}

/**
Expand Down Expand Up @@ -966,6 +974,13 @@ export interface ReactNativeAsyncStorage {
removeItem(key: string): Promise<void>;
}

export interface FirebaseToken {
// The firebase access token (JWT signed by Firebase Auth).
readonly token: string;
// The time when the access token expires.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this in seconds? Asking because I see you're * 1000 in the above change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's a timestamp (in milliseconds since epoch)? Could we clarify that in a comment to be more clear? Thanks!

readonly expirationTime: number;
}

/**
* A user account.
*
Expand Down
Loading