Firebase Authentication

Integrate Firebase Authentication with your Xams application for secure user management.

Xams provides built-in Firebase authentication support with a customizable login page and comprehensive auth flows including multi-factor authentication (MFA).

Use the Xams Firebase template which includes a complete example. Follow the instructions in the README.md file to setup.

User Authentication Helper

The UserUtil class executes with every request to a Xams endpoint and handles user authentication, automatically creating Xams users from Firebase claims:

Project / UserUtil.cs

public class UserUtil
{
private static ConcurrentDictionary<string, Guid> _users = new();
public static async Task<Guid> GetUserId(HttpContext httpContext)
{
var userIdClaim = httpContext.User.Claims
.Where(x => x.Type == ClaimTypes.NameIdentifier)
.Select(x => x.Value).FirstOrDefault();
var emailClaim = httpContext.User.Claims
.Where(x => x.Type == ClaimTypes.Email)
.Select(x => x.Value).FirstOrDefault();
if (userIdClaim == null)
{
throw new Exception("UserId not found");
}
using (var db = new DataContext())
{
var userId = await GetUserId(db, userIdClaim, emailClaim);
try
{
// Attempt to create the user
if (userId == Guid.Empty)
{
userId = await CreateUser(db, userIdClaim, emailClaim);
}
}
catch (Exception e)
{
// Wait a variable amount of time in case multiple
// requests are trying to create the user at once
var rnd = new Random(DateTime.Now.Millisecond);
await Task.Delay(rnd.Next(20, 150));
// Reattempt to retrieve
userId = await GetUserId(db, userIdClaim, emailClaim);
if (userId == Guid.Empty)
{
userId = await CreateUser(db, userIdClaim, emailClaim);
}
}
return userId;
}
}
private static async Task<Guid> GetUserId(DataContext db, string? userIdClaim, string? emailClaim)
{
Guid userId;
if (userIdClaim != null)
{
if (_users.TryGetValue(userIdClaim, out userId))
{
return userId;
}
}
userId = await db.Users
.Where(x => x.FirebaseId == userIdClaim)
.Select(x => x.UserId)
.FirstOrDefaultAsync();
if (userId == Guid.Empty)
{
userId = await db.Users
.Where(x => x.EmailAddress == emailClaim)
.Select(x => x.UserId)
.FirstOrDefaultAsync();
}
return userId;
}
private static async Task<Guid> CreateUser(DataContext db, string userIdClaim, string? emailClaim)
{
var user = new AppUser();
user.FirebaseId = userIdClaim;
user.Name = emailClaim;
user.EmailAddress = emailClaim;
user.CreatedDate = DateTime.UtcNow;
db.Add(user);
await db.SaveChangesAsync();
// Assign default Roles
if (emailClaim == "xxx@xxx.io")
{
var adminRole = new UserRole<AppUser, Role>();
adminRole.UserId = user.UserId;
adminRole.RoleId = SystemRecords.SystemAdministratorRoleId; // Admin role
db.Add(adminRole);
}
await db.SaveChangesAsync();
_users.TryAdd(userIdClaim, user.UserId);
return user.UserId;
}
}

Branding

Use the authentication pages to set branding.

src/pages/login.tsx

import React from 'react'
import { useRouter } from 'next/router'
import { LoginPage } from '@ixeta/xams-firebase'
import { Logo } from '../components/common/Logo'
const Login = () => {
const router = useRouter()
return (
<LoginPage
onLoginSuccess={() => {
const redirectUrl = localStorage.getItem('postLoginRedirect')
localStorage.removeItem('postLoginRedirect')
router.push(redirectUrl || '/app/index')
}}
Logo={<Logo />}
backgroundColor="#2ea2b0"
radius={'md'}
/>
)
}
export default Login

src/pages/profile.tsx

import React from 'react'
import { ProfilePage } from '@ixeta/xams-firebase'
const Profile = () => {
return <ProfilePage Logo={<Logo />} backgroundColor="#2ea2b0" radius={'md'} />
}
export default Profile

src/pages/reset-password.tsx

import React from 'react'
import { ResetPasswordPage } from '@ixeta/xams-firebase'
const ResetPassword = () => {
return (
<ResetPasswordPage
Logo={<Logo />}
backgroundColor="#2ea2b0"
radius={'md'}
/>
)
}
export default ResetPassword

src/pages/__/auth/action.tsx

import React from 'react'
import { ActionPage } from '@ixeta/xams-firebase'
const AuthAction = () => {
return <ActionPage Logo={<Logo />} backgroundColor="#2ea2b0" radius={'md'} />
}
export default AuthAction

React Protected Page

Use the useAuthProtect hook to protect pages and access user information:

src/pages/admin.tsx

import { AdminDashboard } from '@ixeta/xams'
import React from 'react'
import { NavLink } from '@mantine/core'
import { IconLogout } from '@tabler/icons-react'
import { useRouter } from 'next/router'
import Loading from '@/components/common/Loading'
import { useAuthProtect } from '@ixeta/xams-firebase'
const Admin = () => {
const router = useRouter()
const auth = useAuthProtect()
if (auth.isLoading || !router.isReady) {
return <Loading />
}
if (auth.isError) {
return <div>Error loading auth settings</div>
}
if (!auth.isLoggedIn) {
localStorage.setItem('postLoginRedirect', router.asPath)
router.push('/login')
return <></>
}
return (
<AdminDashboard
addMenuItems={[
{
order: 10000,
navLink: (
<NavLink
label="Logout"
leftSection={<IconLogout size={16} />}
onClick={() => {
auth.signOut()
router.push('/')
}}
/>
),
},
]}
/>
)
}
export default Admin

Using Tanstack Query with Authentication

When using Tanstack Query with authenticated pages, always set enabled: auth.isLoggedIn to prevent queries from running before authentication is ready. All hooks must be called at the top level before any conditional returns.

src/pages/dashboard.tsx

import { Query, useAuthRequest } from '@ixeta/xams'
import { useAuthProtect } from '@ixeta/xams-firebase'
import { useQuery } from '@tanstack/react-query'
import { useRouter } from 'next/router'
const Dashboard = () => {
const router = useRouter()
const auth = useAuthProtect()
const authRequest = useAuthRequest()
const { data, isLoading } = useQuery({
queryKey: ['my-data'],
queryFn: async () => {
const readRequest = new Query(['*'])
.from('MyTable')
.top(10)
.toReadRequest()
const response = await authRequest.read(readRequest)
if (!response.succeeded) throw new Error(response.friendlyMessage)
return response.data.results
},
enabled: auth.isLoggedIn, // Prevents query until authenticated
})
// Auth checks come after all hooks
if (auth.isLoading || !router.isReady) {
return <div>Loading...</div>
}
if (!auth.isLoggedIn) {
localStorage.setItem('postLoginRedirect', router.asPath)
router.push('/login')
return <></>
}
return <div>{/* Use data here */}</div>
}

Was this page helpful?