Skip to content

Commit

Permalink
fix: auth_time emulator behavior should match production (auth_time i…
Browse files Browse the repository at this point in the history
…s now set to user's last sign in time). (firebase#3608)
  • Loading branch information
Lovelle Cardoso committed Jul 27, 2021
1 parent e4ab4cc commit 8a9a96b
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
- Makes auth_time emulator behavior match production (auth_time is now set to user's last sign in time). (#3608)
- Fixes Auth Emulator errors when importing many users. (#3577)
- Fixes support for `--except` flag when used for deploying Hosting. (#3397)
7 changes: 6 additions & 1 deletion src/emulator/auth/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1726,7 +1726,11 @@ function generateJwt(
// This field is only set for anonymous sign-in but not for any other
// provider (such as email or Google) in production. Let's match that.
provider_id: signInProvider === "anonymous" ? signInProvider : undefined,
auth_time: toUnixTimestamp(new Date()),
auth_time: user.lastLoginAt
? toUnixTimestamp(new Date(user.lastLoginAt))
: user.lastRefreshAt
? toUnixTimestamp(new Date(user.lastRefreshAt))
: toUnixTimestamp(new Date()),
user_id: user.localId,
firebase: {
identities,
Expand Down Expand Up @@ -2122,6 +2126,7 @@ export interface FirebaseJwtPayload {
exp: number; // expiresAt (in seconds since epoch)
iss: string; // issuer
aud: string; // audience (=projectId)
auth_time: number; // lastLoginAt (in seconds since epoch)
// ...and other fields that we don't care for now.

// Firebase-specific fields:
Expand Down
32 changes: 31 additions & 1 deletion src/test/emulators/auth/misc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { expect } from "chai";
import { decode as decodeJwt, JwtHeader } from "jsonwebtoken";
import { UserInfo } from "../../../emulator/auth/state";
import { PROJECT_ID, signInWithPhoneNumber, TEST_PHONE_NUMBER } from "./helpers";
import {
getAccountInfoByIdToken,
PROJECT_ID,
signInWithPhoneNumber,
TEST_PHONE_NUMBER,
} from "./helpers";
import { describeAuthEmulator } from "./setup";
import {
expectStatusCode,
Expand Down Expand Up @@ -39,6 +44,31 @@ describeAuthEmulator("token refresh", ({ authApi }) => {
});
});

it("should populate auth_time to match lastLoginAt (in seconds since epoch)", async () => {
const emailUser = { email: "alice@example.com", password: "notasecret" };
const { refreshToken } = await registerUser(authApi(), emailUser);

const res = await authApi()
.post("/securetoken.googleapis.com/v1/token")
.type("form")
// snake_case parameters also work, per OAuth 2.0 spec.
.send({ refresh_token: refreshToken, grantType: "refresh_token" })
.query({ key: "fake-api-key" });

const idToken = res.body.id_token;
const user = await getAccountInfoByIdToken(authApi(), idToken);
const lastLoginAtSeconds = user.lastLoginAt
? toUnixTimestamp(new Date(user.lastLoginAt))
: undefined;
const decoded = decodeJwt(idToken, { complete: true }) as {
header: JwtHeader;
payload: FirebaseJwtPayload;
} | null;
expect(decoded, "JWT returned by emulator is invalid").not.to.be.null;
expect(decoded!.header.alg).to.eql("none");
expect(decoded!.payload.auth_time).to.equal(lastLoginAtSeconds);
});

it("should error if user is disabled", async () => {
const { refreshToken, localId } = await registerAnonUser(authApi());
await updateAccountByLocalId(authApi(), localId, { disableUser: true });
Expand Down

0 comments on commit 8a9a96b

Please sign in to comment.