backend-app-api,auth: move token typ claim to be a header param

Co-authored-by: Camila Belo <camilaibs@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-04-02 17:40:21 +02:00
committed by Camila Belo
parent 018b0910e0
commit 0d2a05418b
3 changed files with 22 additions and 26 deletions
@@ -22,16 +22,13 @@ import {
BackstageServicePrincipal,
BackstageNonePrincipal,
BackstageUserPrincipal,
IdentityService,
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { AuthenticationError } from '@backstage/errors';
import {
IdentityApiGetIdentityRequest,
TokenTypes,
} from '@backstage/plugin-auth-node';
import { base64url, decodeJwt } from 'jose';
import { TokenTypes } from '@backstage/plugin-auth-node';
import { base64url, decodeJwt, decodeProtectedHeader } from 'jose';
import { UserTokenHandler } from './UserTokenHandler';
/** @internal */
export type InternalBackstageCredentials<TPrincipal = unknown> =
@@ -115,7 +112,8 @@ class DefaultAuthService implements AuthService {
// allowLimitedAccess is currently ignored, since we currently always use the full user tokens
async authenticate(token: string): Promise<BackstageCredentials> {
const { typ, sub, aud } = decodeJwt(token);
const { typ } = decodeProtectedHeader(token);
const { sub, aud } = decodeJwt(token);
if (typ) {
switch (typ) {
@@ -244,13 +242,13 @@ class DefaultAuthService implements AuthService {
const limitedUserToken = [
base64url.encode(
JSON.stringify({
typ: 'vnd.backstage.limited-user',
alg: header.alg,
kid: header.kid,
}),
),
base64url.encode(
JSON.stringify({
typ: 'vnd.backstage.limited-user',
sub: payload.sub,
ent: payload.ent,
iat: payload.iat,
@@ -115,14 +115,17 @@ export class TokenFactory implements TokenIssuer {
const signingKey = await importJWK(key);
const uip = await this.createUserIdentityClaim({
header: { alg: key.alg, kid: key.kid },
payload: { typ: TokenTypes.limitedUser.typClaim, sub, ent, iat, exp },
header: {
typ: TokenTypes.limitedUser.typParam,
alg: key.alg,
kid: key.kid,
},
payload: { sub, ent, iat, exp },
key: signingKey,
});
const claims: BackstageTokenPayload = {
...additionalClaims,
typ: TokenTypes.user.typClaim,
iss,
sub,
ent,
@@ -133,7 +136,11 @@ export class TokenFactory implements TokenIssuer {
};
const token = await new SignJWT(claims)
.setProtectedHeader({ alg: key.alg, kid: key.kid })
.setProtectedHeader({
typ: TokenTypes.user.typParam,
alg: key.alg,
kid: key.kid,
})
.sign(signingKey);
if (token.length > MAX_TOKEN_LENGTH) {
@@ -243,6 +250,7 @@ export class TokenFactory implements TokenIssuer {
// JWS.
private async createUserIdentityClaim(options: {
header: {
typ: string;
alg: string;
kid?: string;
};
@@ -257,12 +265,12 @@ export class TokenFactory implements TokenIssuer {
// then append the signature.
const header = {
typ: options.header.typ,
alg: options.header.alg,
...(options.header.kid ? { kid: options.header.kid } : {}),
};
const payload = {
typ: options.payload.typ,
sub: options.payload.sub,
ent: options.payload.ent,
iat: options.payload.iat,
+3 -13
View File
@@ -383,14 +383,14 @@ export type CookieConfigurer = (ctx: {
*/
export const TokenTypes = Object.freeze({
user: Object.freeze({
typClaim: 'vnd.backstage.user',
typParam: 'vnd.backstage.user',
audClaim: 'backstage',
}),
limitedUser: Object.freeze({
typClaim: 'vnd.backstage.limited-user',
typParam: 'vnd.backstage.limited-user',
}),
service: Object.freeze({
typClaim: 'vnd.backstage.service',
typParam: 'vnd.backstage.service',
}),
});
@@ -400,11 +400,6 @@ export const TokenTypes = Object.freeze({
* @public
*/
export interface BackstageTokenPayload {
/**
* The token type
*/
typ: typeof TokenTypes.user.typClaim;
/**
* The issuer of the token, currently the discovery URL of the auth backend
*/
@@ -452,11 +447,6 @@ export interface BackstageTokenPayload {
* @public
*/
export interface BackstageUserIdentityProofPayload {
/**
* The token type
*/
typ: typeof TokenTypes.limitedUser.typClaim;
/**
* The entity ref of the user
*/