-
Go to GCP console and enable the Identity Platform.

-
Add an Email/Password provider in Identity Platform page:

-
Make sure to enable the Email/Password provider as the screenshot below:

Set the following two environment variables:
export AUTH_AUTO_CREATE_USERS=true
export AUTH_EMAIL_DOMAINS_WHITELIST=google.com
- Please note that
AUTH_EMAIL_DOMAINS_WHITELISTmatches exact email domain, not wildcard support yet.
Please verify that email/password provider is enabled.
You can add users to the platform in the firebase console.
- Navigate to the console and select your project
- Navigate to the Authentication section (under Build > Authentication)
- Click "Add User" on the UX.

- Enter the email and password for the user.
- You can send a password reset email to the user via firebase so the user can create their own password.
- The user can now log in via the UX apps for the platform.
- A user model will be created for the user when they log in. Note that user model will be populated with test first name/last name and should be updated if you care about those fields.
Please verify that email/password provider is enabled.
Get the IP address for the GKE ingress endpoint:
IP_ADDRESS=$(gcloud compute addresses describe gke-ingress-ip --global --format='value(address)')
BASE_URL="http://$IP_ADDRESS"
In the source code folder:
pip install -r components/common/requirements.txt
pip install -r components/authentication/requirements.txt
PYTHONPATH=components/common/src/ python components/authentication/scripts/user_tool.py create_user --base-url=$BASE_URL
- You can use the IP address, e.g. http://127.0.0.1/
- This will register the user to Identity Platform and a user record in Firestore (in
userscollection).
Once complete, it will show the ID token in the output. E.g.:
User 'user@my.domain.com' created successfully. ID Token:
<my-id-token...>
Get an access token for a particular user:
PYTHONPATH=components/common/src/ python components/authentication/scripts/user_tool.py get_token
- This will print out the token in the terminal.
Signed in with existing user 'user@my.domain.com'. ID Token:
<my-id-token...>
Difference Between OAuth, OpenID Connect, and SAML - helpful background
Code involved in this flow:
main.pyof a GENIE servicecommon.utils.auth_service.validate_tokencommon.utils.auth_service.validate_oauth_token(unless it is a service account). This calls the GENIE /validate API endpoint.
components/authentication/src/routes/validate_token.validate_id_tokencomponents/authentication/src/services/validation_service.validate_tokencommon.utils.cache_service.get_token_cachefirebase.admin.auth.verify_id_token
common.utils.user_handler.get_user_by_email
Explanation of each step:
- main.py. The main.py of every service sets up the FastAPI router for that service using code similar to this, which is from
components/llm_service/src/main.py(see below).Depends(valdate_token)is the FastAPI syntax that requires that every call to the API first invokescommon.utils.auth_service.validate_tokenfunction with the payload.
api = FastAPI(
title=service_title,
version="latest",
# docs_url=None,
# redoc_url=None,
dependencies=[Depends(validate_token)]
)
common.utils.auth_service.validate_token. This function callscommon.utils.auth_service.validate_oauth_token, which checks for a credentials entry in the payload, and constructs the URL of the GENIE authentication service/validateendpoint. It passes the scheme and the credentials as part of the Authorization headers. The/validateendpoint is mapped to thecomponents/authentication/src/routes/validate_tokenfunction.
if not token:
raise TokenNotFoundError("Unauthorized: token is empty.")
token_dict = dict(token)
if token_dict["credentials"]:
api_endpoint = f"http://{AUTH_SERVICE_NAME}/{AUTH_SERVICE_NAME}/" \
"api/v1/validate"
res = requests.get(
url=api_endpoint,
headers={
"Content-Type": "application/json",
"Authorization":
f"{token_dict['scheme']} {token_dict['credentials']}"
},
timeout=60)
data = res.json()
components/authentication/src/routes/validate_token. This function does two things:
- Calls
components/authentication/src/services/validation_service.validate_token(see code below)- Checks for it in the redis cache - uses
common.utils.cache_service.get_token_cache - If its not in the cache, calls
firebase.admin.auth.verify_id_token. Any errors raised fromverify_id_tokenare not caught here and will propogate back to the calling function - Returns a decoded token
- Checks for it in the redis cache - uses
token = bearer_token
decoded_token = None
cached_token = get_token_cache(f"cache::{token}")
decoded_token = cached_token if cached_token else verify_id_token(token)
if not cached_token:
cached_token = set_token_cache(f"cache::{token}", decoded_token)
Logger.info(f"Id Token caching status: {cached_token}")
Logger.info(f"Id Token: {decoded_token}")
return decoded_token
- Calls
common.utils.user_handler.get_user_by_emailto make sure the user is in firestore
src\navigation\SignInForm.tsx- src\contexts\auth.tsx
- src\utils\api.ts
- src\utils\firebase.ts
For the RIT standard SPA starter app the sign in code is in src\navigation\SignInForm.tsx
The code handles both sign in with Google and sign in with email and password, using firebase/auth functions (see below). It does not use the GENIE /signin endpoint.
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signInWithPopup,
} from "firebase/auth"
Pages that import firebase/auth
- webapp\src\navigation\SignInForm.tsx
- webapp\src\contexts\auth.tsx
Note that many other nav components (Sidebard, etc), take firebase/auth/User as a prop.
import { User, onIdTokenChanged } from "firebase/auth"
src\routes\AIRoute.tsx:
- calls
useAuthinsrc\contexts\auth.tsxto get the token from theAuthContext- creates an
AuthContextfirebase.auth.User, token string, loading flag
- creates an
AuthProviderthat- listens for
onIdTokenChangeand then callsuser.getIdToken - refreshes token every 10 minues calling
user.getIdToken
- listens for
- the
useAuthfunction returns a 'full'AuthContext
- creates an
- embeds the
src/components/chat/Chatcomponent and passes it theuserTokenfromuseAuth()
src/components/chat/Chat.tsx:
-
calls
fetchQuery, createQuery, resumeQueryfromutils\api.ts- calls
src/utils\api.ts:- The base URL to the NASA GENIE endpoint is stored in
VITE_PUBLIC_API_ENDPOINTand is defined in:.env.demo .env.prod .env.dev - uses the base
endpointto build the full URL to individual GENIE endpoints (code example below). The users token is passed as a Bearer token in the header.
- The base URL to the NASA GENIE endpoint is stored in
export const fetchQuery = (token: string, queryId: string | null) => (): Promise<Query | undefined | null> => { if (!queryId) return Promise.resolve(null) const url = `${endpoint}/query/${queryId}` const headers = { Authorization: `Bearer ${token}` } return axios.get(url, { headers }).then(path(["data", "data"])) }There is also
src/utils\firebase.tsthat sets up firebase objects. - calls
- Add provider initialization to src/utils/firebase.ts
const microsoftProvider = new OAuthProvider('microsoft.com');
microsoftProvider.setCustomParameters({
// Optional "tenant" parameter in case you are using an Azure AD tenant.
// eg. '8eaef023-2b34-4da1-9baa-8bc8c9d6a490' or 'contoso.onmicrosoft.com'
// or "common" for tenant-independent tokens.
// The default value is "common".
tenant: 'e2ccceae-06d8-4f6c-861a-d0292a5fe32a'
});
And make sure it is exported.
-
Update
src/utils/types.tsto include the name of the new provider inIAuthProvidertype. -
Update
src/utils/Appconfig.tsto include the name of the new provider inauthProvidersfield. -
Update
src/navigation/SignInForm.tsx. Add code to render the new provider sign in and to submit it. -
Add to AUTHORIZED_DOMAIN environment variable the new email domain.