I have have an NextJS application that uses some proxy rewrite to add auth headers from session to request header. This works fine on pages that do not use SSR.
When i try to make a request via apollo client in getServerSideProps, then getSession is suddenly null in the api route.
api route code:
import { getSession } from 'next-auth/react';
import httpProxyMiddleware from 'next-http-proxy-middleware';
import readDockerSecret from 'Utils/secret';
const middleware = async (req: NextApiRequest, res: NextApiResponse) => {
const session = await getSession({ req });
const token = session?.access_token;
if (!session) console.log('No session❤️🔥');
if (session) console.log('Got session🥑');
return httpProxyMiddleware(req, res, {
target: readDockerSecret('NEXUS_GRAPHQL_REWRITE_URL') || process.env.NEXUS_GRAPHQL_REWRITE_URL,
pathRewrite: {
'^/api/graphql': ''
// changeOrigin: true
// proxyTimeout: 5000
// secure: true
},
headers: { Authorization: `${token}` }
// headers: { Authorization: `${token}` }
});
};
export default middleware;```
Is there something i have to do to make sure i have a session when using ssr in nextjs (with nextauth)?
My Next.js SSR App will use Firebase Auth to authenticate users. The access token from Firebase Auth will be used to authenticate on an external API.
I have it working in my React App but I am migrating now to Next.js for SSR.
Currently I am struggling with the Axios interceptor... how do I authenticate the user and add the Firebase access token to the request header when on server-side?
I think I need to use cookies?!
High level explanation would sufficient, some code example even better!
edit: I found this article but that would mean I have to add this logic to every single protected route and additionally add the access token to the Axios interceptor for client-side requests, eg. when searching or sending a post request?
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
try {
const cookies = nookies.get(ctx);
const token = await firebaseAdmin.auth().verifyIdToken(cookies.token);
// the user is authenticated!
const { uid, email } = token;
// FETCH STUFF HERE!! 🚀
return {
props: { message: `Your email is ${email} and your UID is ${uid}.` },
};
} catch (err) {
// either the `token` cookie didn't exist
// or token verification failed
// either way: redirect to the login page
ctx.res.writeHead(302, { Location: '/login' });
ctx.res.end();
// `as never` prevents inference issues
// with InferGetServerSidePropsType.
// The props returned here don't matter because we've
// already redirected the user.
return { props: {} as never };
}
};
I am using the Firebase Admin SDK for my server — in my case Apollo Server running in google cloud functions. Upon login, the server generates a token and returns it to the client. The client puts it into local storage and uses it for future requests. This works fine until the token expires 1 hour later.
How can I refresh the token so that the user doesn't need to keep logging in every hour?
On the server, login returns a token; the client will put it in local storage
exports.login = async (email, password) => {
const data = await firebase
.auth()
.signInWithEmailAndPassword(email, password);
const token = await data.user.getIdToken();
return token;
}
On the client, ApolloClient includes the token with auth headers
I'm using Gatsby in this example, but I've had the same trouble doing this with create-react-app.
import fetch from 'isomorphic-fetch';
import {ApolloClient, HttpLink, InMemoryCache} from '#apollo/client';
import {setContext} from 'apollo-link-context';
const httpLink = new HttpLink({
uri: `${process.env.GATSBY_API_URL}`,
fetch,
});
const authLink = setContext((_, {headers}) => {
const token = localStorage.getItem('myUserToken');
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
export const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
connectToDevTools: true,
});
One potential solution might be, if I didn't use Graphql and ApolloClient, to import firebase.auth on the client and generate a refreshed auth token before every request. For example:
// firebase_service.js
import * as firebase from 'firebase/app';
import 'firebase/auth';
import { config } from './firebase-config';
firebase.initializeApp(config);
export const auth = firebase.auth;
// getData.js
import { auth } from '../firebase_service';
import axios from 'axios';
const getData = () => {
const token = await auth().currentUser.getIdToken(true);
return axios
.get(FIREBASE_API_URL, {
headers: { authorization: `Bearer ${token}` },
})
.then((res) => res.data);
}
Would there be a way to do something like this but with ApolloClient, so that the headers include a fresh token when appropriate?
I am getting cookie inside my _app.jsx but I am unable to send it to my custom API middleware.
So cookie needs to be there in the middleware but how tp pass it to middleware when its SSR. I have seen examples but they are not helping
I am using unfetch to call api in that middleware and I need token when user opens a page which supports SSR and I do all my api calls from middleware.
export default withRedux(initStore)(
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
const cookie = nextCookie(ctx);
return {
pageProps: Component.getInitialProps
? await Component.getInitialProps(ctx)
: {}
};
}
}
);
With the new firebase cloud function I've decided to move some of my HTTP endpoint to firebase.
Everything works great... But i have the following issue. I have two endpoints build by HTTP Triggers (Cloud Functions)
An API endpoint to create users and returns the custom Token
generated by Firebase Admin SDK.
An API endpoint to fetch certain user details.
While the first endpoint is fine, but for my second end point i would want to protect it for authenticated users only. meaning someone who has the token i generated earlier.
How do i go about solving this?
I know we can get the Header parameters in the cloud function using
request.get('x-myheader')
but is there a way to protect the endpoint just like protecting the real time data base?
There is an official code sample for what you're trying to do. What it illustrates is how to set up your HTTPS function to require an Authorization header with the token that the client received during authentication. The function uses the firebase-admin library to verify the token.
Also, you can use "callable functions" to make a lot of this boilerplate easier, if your app is able to use Firebase client libraries.
As mentioned by #Doug, you can use firebase-admin to verify a token. I've set up a quick example:
exports.auth = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];
return admin.auth().verifyIdToken(tokenId)
.then((decoded) => res.status(200).send(decoded))
.catch((err) => res.status(401).send(err));
});
});
In the example above, I've also enabled CORS, but that's optional. First, you get the Authorization header and find out the token.
Then, you can use firebase-admin to verify that token. You'll get the decoded information for that user in the response. Otherwise, if the token isn't valid, it'll throw an error.
As also mentioned by #Doug,
you can use Callable Functions in order to exclude some boilerplate code from your client and your server.
Example callable function:
export const getData = functions.https.onCall((data, context) => {
// verify Firebase Auth ID token
if (!context.auth) {
return { message: 'Authentication Required!', code: 401 };
}
/** This scope is reachable for authenticated users only */
return { message: 'Some Data', code: 200 };
});
It can be invoked directly from you client like so:
firebase.functions().httpsCallable('getData')({query}).then(result => console.log(result));
The above methods authenticate the user using logic inside the function, so the function must be still be invoked to do the checking.
That's a totally fine method, but for the sake of comprehensivity, there is an alternative:
You can set a function to be "private" so that it can't be invoked except by registered users (you decide on permissions). In this case, unauthenticated requests are denied outside the context of the function, and the function is not invoked at all.
Here are references to (a) Configuring functions as public/private, and then (b) authenticating end-users to your functions.
Note that the docs above are for Google Cloud Platform, and indeed, this works because every Firebase project is also a GCP project. A related caveat with this method is that, as of writing, it only works with Google-account based authentication.
In Firebase, in order to simplify your code and your work, it's just a matter of architectural design:
For public accessible sites/contents, use HTTPS triggers with Express. To restrict only samesite or specific site only, use CORS to control this aspect of security. This make sense because Express is useful for SEO due to its server-side rendering content.
For apps that require user authentication, use HTTPS Callable Firebase Functions, then use the context parameter to save all the hassles. This also makes sense, because such as a Single Page App built with AngularJS -- AngularJS is bad for SEO, but since it's a password protected app, you don't need much of the SEO either. As for templating, AngularJS has built-in templating, so no need for sever-side template with Express. Then Firebase Callable Functions should be good enough.
With the above in mind, no more hassle and make life easier.
There is a lot of great information here that really helped me, but I thought it might be good to break down a simple working example for anyone using Angular attempting this for the first time. The Google Firebase documentation can be found at https://firebase.google.com/docs/auth/admin/verify-id-tokens#web.
//#### YOUR TS COMPONENT FILE #####
import { Component, OnInit} from '#angular/core';
import * as firebase from 'firebase/app';
import { YourService } from '../services/yourservice.service';
#Component({
selector: 'app-example',
templateUrl: './app-example.html',
styleUrls: ['./app-example.scss']
})
export class AuthTokenExample implements OnInit {
//property
idToken: string;
//Add your service
constructor(private service: YourService) {}
ngOnInit() {
//get the user token from firebase auth
firebase.auth().currentUser.getIdToken(true).then((idTokenData) => {
//assign the token to the property
this.idToken = idTokenData;
//call your http service upon ASYNC return of the token
this.service.myHttpPost(data, this.idToken).subscribe(returningdata => {
console.log(returningdata)
});
}).catch((error) => {
// Handle error
console.log(error);
});
}
}
//#### YOUR SERVICE #####
//import of http service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class MyServiceClass {
constructor(private http: HttpClient) { }
//your myHttpPost method your calling from your ts file
myHttpPost(data: object, token: string): Observable<any> {
//defining your header - token is added to Authorization Bearer key with space between Bearer, so it can be split in your Google Cloud Function
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
//define your Google Cloud Function end point your get from creating your GCF
const endPoint = ' https://us-central1-your-app.cloudfunctions.net/doSomethingCool';
return this.http.post<string>(endPoint, data, httpOptions);
}
}
//#### YOUR GOOGLE CLOUD FUNCTION 'GCF' #####
//your imports
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});
exports.doSomethingCool = functions.https.onRequest((req, res) => {
//cross origin middleware
cors(req, res, () => {
//get the token from the service header by splitting the Bearer in the Authorization header
const tokenId = req.get('Authorization').split('Bearer ')[1];
//verify the authenticity of token of the user
admin.auth().verifyIdToken(tokenId)
.then((decodedToken) => {
//get the user uid if you need it.
const uid = decodedToken.uid;
//do your cool stuff that requires authentication of the user here.
//end of authorization
})
.catch((error) => {
console.log(error);
});
//end of cors
})
//end of function
})
There is a nice official example on it using Express - may be handy in future: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (pasted below just for sure)
Keep in mind that exports.app makes your functions available under /app slug (in this case there is only one function and is available under <you-firebase-app>/app/hello. To get rid of it you actually need to rewrite Express part a bit (middleware part for validation stays the same - it works very good and is quite understandable thanks to comments).
/**
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = async (req, res, next) => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if(req.cookies) {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
res.send(`Hello ${req.user.name}`);
});
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);
My rewrite to get rid of /app:
const hello = functions.https.onRequest((request, response) => {
res.send(`Hello ${req.user.name}`);
})
module.exports = {
hello
}
I have been struggling to get proper firebase authentication in golang GCP function. There is actually no example for that, so I decided to build this tiny library: https://github.com/Jblew/go-firebase-auth-in-gcp-functions
Now you can easily authenticate users using firebase-auth (which is distinct from gcp-authenticated-functions and is not directly supported by the identity-aware-proxy).
Here is an example of using the utility:
import (
firebaseGcpAuth "github.com/Jblew/go-firebase-auth-in-gcp-functions"
auth "firebase.google.com/go/auth"
)
func SomeGCPHttpCloudFunction(w http.ResponseWriter, req *http.Request) error {
// You need to provide 1. Context, 2. request, 3. firebase auth client
var client *auth.Client
firebaseUser, err := firebaseGcpAuth.AuthenticateFirebaseUser(context.Background(), req, authClient)
if err != nil {
return err // Error if not authenticated or bearer token invalid
}
// Returned value: *auth.UserRecord
}
Just keep in mind to deploy you function with --allow-unauthenticated flag (because firebase authentication occurs inside function execution).
Hope this will help you as it helped me. I was determined to use golang for cloud functions for performance reasons — Jędrzej
You can take this as a functions returns boolean. If the user verified or not then you will continue or stop your API. In Addition you can return claims or user result from the variable decode
const authenticateIdToken = async (
req: functions.https.Request,
res: functions.Response<any>
) => {
try {
const authorization = req.get('Authorization');
if (!authorization) {
res.status(400).send('Not Authorized User');
return false;
}
const tokenId = authorization.split('Bearer ')[1];
return await auth().verifyIdToken(tokenId)
.then((decoded) => {
return true;
})
.catch((err) => {
res.status(401).send('Not Authorized User')
return false;
});
} catch (e) {
res.status(400).send('Not Authorized User')
return false;
}
}