Skip to content

Write BYO-CIAM token, expiresIn to the Auth Object initialized #9052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: sammansi-gcip-byociam-web
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs-devsite/_toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ toc:
path: /docs/reference/js/auth.samlauthprovider.md
- title: TenantConfig
path: /docs/reference/js/auth.tenantconfig.md
- title: TokenResponse
path: /docs/reference/js/auth.tokenresponse.md
- title: TotpMultiFactorAssertion
path: /docs/reference/js/auth.totpmultifactorassertion.md
- title: TotpMultiFactorGenerator
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 @@ -33,6 +33,7 @@ export interface Auth
| [settings](./auth.auth.md#authsettings) | [AuthSettings](./auth.authsettings.md#authsettings_interface) | The [Auth](./auth.auth.md#auth_interface) instance's settings. |
| [tenantConfig](./auth.auth.md#authtenantconfig) | [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) | The [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) used to initialize a Regional Auth. This is only present if regional auth is initialized and <code>DefaultConfig.REGIONAL_API_HOST</code> backend endpoint is used. |
| [tenantId](./auth.auth.md#authtenantid) | string \| null | The [Auth](./auth.auth.md#auth_interface) instance's tenant ID. |
| [tokenResponse](./auth.auth.md#authtokenresponse) | [TokenResponse](./auth.tokenresponse.md#tokenresponse_interface) \| null | The token response initialized via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. |

## Methods

Expand Down Expand Up @@ -156,6 +157,18 @@ const result = await signInWithEmailAndPassword(auth, email, password);

```

## Auth.tokenResponse

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 tokenResponse: TokenResponse | null;
```

## Auth.authStateReady()

returns a promise that resolves immediately when the initial auth state is settled. When the promise resolves, the current user might be a valid user or `null` if the user signed out.
Expand Down
1 change: 1 addition & 0 deletions docs-devsite/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Firebase Authentication
| [ReactNativeAsyncStorage](./auth.reactnativeasyncstorage.md#reactnativeasyncstorage_interface) | Interface for a supplied <code>AsyncStorage</code>. |
| [RecaptchaParameters](./auth.recaptchaparameters.md#recaptchaparameters_interface) | Interface representing reCAPTCHA parameters.<!-- -->See the [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) for the list of accepted parameters. All parameters are accepted except for <code>sitekey</code>: Firebase Auth provisions a reCAPTCHA for each project and will configure the site key upon rendering.<!-- -->For an invisible reCAPTCHA, set the <code>size</code> key to <code>invisible</code>. |
| [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) | The tenant config that can be used to initialize a Regional [Auth](./auth.auth.md#auth_interface) instance. |
| [TokenResponse](./auth.tokenresponse.md#tokenresponse_interface) | Interface for TokenRespone returned via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. This is expected to be returned only if [Auth](./auth.auth.md#auth_interface) object initialized has defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface)<!-- -->. |
| [TotpMultiFactorAssertion](./auth.totpmultifactorassertion.md#totpmultifactorassertion_interface) | The class for asserting ownership of a TOTP second factor. Provided by [TotpMultiFactorGenerator.assertionForEnrollment()](./auth.totpmultifactorgenerator.md#totpmultifactorgeneratorassertionforenrollment) and [TotpMultiFactorGenerator.assertionForSignIn()](./auth.totpmultifactorgenerator.md#totpmultifactorgeneratorassertionforsignin)<!-- -->. |
| [TotpMultiFactorInfo](./auth.totpmultifactorinfo.md#totpmultifactorinfo_interface) | The subclass of the [MultiFactorInfo](./auth.multifactorinfo.md#multifactorinfo_interface) interface for TOTP second factors. The <code>factorId</code> of this second factor is [FactorId](./auth.md#factorid)<!-- -->.TOTP. |
| [User](./auth.user.md#user_interface) | A user account. |
Expand Down
42 changes: 42 additions & 0 deletions docs-devsite/auth.tokenresponse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 %}

# TokenResponse interface
Interface for TokenRespone returned via [exchangeToken()](./auth.md#exchangetoken_b6b1871) endpoint. This is expected to be returned only if [Auth](./auth.auth.md#auth_interface) object initialized has defined [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface)<!-- -->.

<b>Signature:</b>

```typescript
export interface TokenResponse
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [expiresIn](./auth.tokenresponse.md#tokenresponseexpiresin) | string | |
| [token](./auth.tokenresponse.md#tokenresponsetoken) | string | |

## TokenResponse.expiresIn

<b>Signature:</b>

```typescript
readonly expiresIn: string;
```

## TokenResponse.token

<b>Signature:</b>

```typescript
readonly token: string;
```
2 changes: 1 addition & 1 deletion packages/auth/src/api/authentication/exchange_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface ExchangeTokenRequest {

export interface ExchangeTokenRespose {
accessToken: string;
expiresIn?: string;
expiresInSec: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not store this as a Number instead of string?

Copy link
Contributor

Choose a reason for hiding this comment

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

}

export async function exchangeToken(
Expand Down
15 changes: 14 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,
TokenResponse
} from '../../model/public_types';
import {
createSubscribe,
Expand Down Expand Up @@ -99,6 +100,7 @@ export const enum DefaultConfig {
export class AuthImpl implements AuthInternal, _FirebaseService {
currentUser: User | null = null;
emulatorConfig: EmulatorConfig | null = null;
tokenResponse: TokenResponse | null = null;
private operations = Promise.resolve();
private persistenceManager?: PersistenceUserManager;
private redirectPersistenceManager?: PersistenceUserManager;
Expand Down Expand Up @@ -454,6 +456,12 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
});
}

async _updateTokenResponse(tokenResponse: TokenResponse): Promise<void> {
if (tokenResponse) {
this.tokenResponse = tokenResponse;
}
}

async signOut(): Promise<void> {
if (_isFirebaseServerApp(this.app)) {
return Promise.reject(
Expand Down Expand Up @@ -621,6 +629,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
}
}

async getTokenForRegionalAuth():
Promise<string> {
if (Date.now() > this.tokenResponse?.expiresIn)
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks incomplete

}

toJSON(): object {
return {
apiKey: this.config.apiKey,
Expand Down
24 changes: 24 additions & 0 deletions packages/auth/src/core/auth/firebase_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export class AuthInterop implements FirebaseAuthInternal {
): Promise<{ accessToken: string } | null> {
this.assertAuthConfigured();
await this.auth._initializationPromise;
if (this.auth.tenantConfig) {
return this.getTokenRegionalAuth();
}
if (!this.auth.currentUser) {
return null;
}
Expand All @@ -51,6 +54,23 @@ export class AuthInterop implements FirebaseAuthInternal {
return { accessToken };
}

async getTokenRegionalAuth() :
Promise<{ accessToken: string } | null> {
this.assertRegionalAuthConfigured();
if (!this.auth.tokenResponse) {
return null;
}

if (!this.auth.tokenResponse.expirationTime ||
Date.now() > this.auth.tokenResponse.expirationTime) {
await this.auth._updateTokenResponse(null);
return null;
}

const accessToken = await this.auth.tokenResponse.token;
return { accessToken };
}

addAuthTokenListener(listener: TokenListener): void {
this.assertAuthConfigured();
if (this.internalListeners.has(listener)) {
Expand Down Expand Up @@ -85,6 +105,10 @@ export class AuthInterop implements FirebaseAuthInternal {
);
}

private assertRegionalAuthConfigured(): void {
_assert(this.auth.tenantConfig, AuthErrorCode.OPERATION_NOT_ALLOWED);
}

private updateProactiveRefresh(): void {
if (this.internalListeners.size > 0) {
this.auth._startProactiveRefresh();
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/core/strategies/exchange_token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ describe('core/strategies/exchangeToken', () => {
'projects/test-project-id/locations/us/tenants/tenant-1/idpConfigs/idp-config',
token: 'custom-token'
});
expect(regionalAuth.tokenResponse?.token).to.equal('outbound-token');
expect(regionalAuth.tokenResponse?.expiresIn).to.equal('1000');
expect(mock.calls[0].method).to.eq('POST');
expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq(
'application/json'
Expand Down
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._updateTokenResponse({
token: token.accessToken,
expirationTime: Date.now() + Number(token.expiresInSec) * 1000
});
}
return token.accessToken;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/auth/src/model/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
PasswordValidationStatus,
PopupRedirectResolver,
TenantConfig,
TokenResponse,
User
} from './public_types';
import { ErrorFactory } from '@firebase/util';
Expand Down Expand Up @@ -66,6 +67,7 @@ export interface ConfigInternal extends Config {
export interface AuthInternal extends Auth {
currentUser: User | null;
emulatorConfig: EmulatorConfig | null;
tokenResponse: TokenResponse | null;
_agentRecaptchaConfig: RecaptchaConfig | null;
_tenantRecaptchaConfigs: Record<string, RecaptchaConfig>;
_projectPasswordPolicy: PasswordPolicy | null;
Expand All @@ -75,6 +77,7 @@ export interface AuthInternal extends Auth {
_initializationPromise: Promise<void> | null;
_persistenceManagerAvailable: Promise<void>;
_updateCurrentUser(user: UserInternal | null): Promise<void>;
_updateTokenResponse(tokenResponse: TokenResponse | null): Promise<void>;

_onStorageEvent(): void;

Expand Down
20 changes: 20 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 tokenResponse: TokenResponse | null;
}

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

/**
* Interface for TokenRespone returned via {@link exchangeToken} endpoint.
* This is expected to be returned only if {@link Auth} object initialized
* has defined {@link TenantConfig}.
*/
export interface TokenResponse {
// The firebase access token (JWT signed by Firebase Auth).
readonly token: string;
// The time when the access token expires.
readonly expirationTime: number;
}

/**
* A user account.
*
Expand Down
1 change: 0 additions & 1 deletion packages/auth/test/helpers/api/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,5 @@ export function mockRegionalEndpointWithParent(
status = 200
): Route {
const url = `${TEST_SCHEME}://${TEST_HOST}${parent}${endpoint}`;
console.log('here ', url);
return mock(url, response, status);
}
Loading