Owner & Authority
Understanding the distinction between owner and authority is crucial for Solana development. These concepts determine who can modify account data and who controls account functionality.
Owner vs Authority: The Core Distinction
Text
OWNER vs AUTHORITY
══════════════════
OWNER (on-chain, protocol level):
├── Stored in account metadata
├── Always a PROGRAM public key
├── Determines which program can modify data
├── Cannot be an EOA (wallet)
└── Set at account creation, rarely changed
AUTHORITY (application level):
├── Stored in account DATA
├── Typically a USER public key
├── Determined by program logic
├── Can be an EOA or PDA
└── Can be changed via program instruction
Visual Comparison
Text
┌────────────────────────────────────────────────────────────────────┐
│ TOKEN ACCOUNT EXAMPLE │
├────────────────────────────────────────────────────────────────────┤
│ │
│ Account Metadata (protocol controlled): │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA │ │
│ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ │ │
│ │ Token Program (PROGRAM, not user) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ Account Data (program controlled): │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ mint: 7i5KKs... │ │
│ │ authority: 9WzDXw... ← USER's wallet (can transfer tokens)│ │
│ │ ^^^^^^^^^ │ │
│ │ This is what people mean by "owner" colloquially│ │
│ │ amount: 1000000000 │ │
│ │ delegate: null │ │
│ │ state: initialized │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
The Owner Field
What Owner Means
TypeScript
// Every account has an owner in its metadata
interface AccountInfo {
lamports: number;
owner: PublicKey; // ← This is the OWNER
data: Uint8Array;
executable: boolean;
rentEpoch: number;
}
// The owner is ALWAYS a program
// It determines which program can:
// - Modify the account's data field
// - Debit lamports from the account
// Common owners:
const SYSTEM_PROGRAM = new PublicKey("11111111111111111111111111111111");
const TOKEN_PROGRAM = new PublicKey(
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
);
const TOKEN_2022 = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
Owner-Based Access Control
Rust
// In a Solana program, the runtime enforces owner checks
// This will FAIL at runtime if account.owner != my_program_id
pub fn modify_data(ctx: Context<ModifyData>) -> Result<()> {
let account = &mut ctx.accounts.my_account;
account.data = new_value; // Only works if we're the owner
Ok(())
}
// Runtime check (conceptual):
fn runtime_check(account: &AccountInfo, program_id: &Pubkey) -> bool {
// Can this program modify this account?
if instruction_modifies_data {
return account.owner == program_id;
}
true
}
Changing Owners
TypeScript
// Owner can only be changed by:
// 1. The current owner program
// 2. The System Program (for accounts it owns)
import { SystemProgram, Transaction } from "@solana/web3.js";
// Transfer ownership to a program
// (Only System Program-owned accounts can do this)
const assignIx = SystemProgram.assign({
accountPubkey: accountToReassign,
programId: newOwnerProgram,
});
// After this, only newOwnerProgram can modify the account
Authority Patterns
Authority is a logical concept implemented in account data, not protocol-enforced.
Single Authority
Rust
// Anchor pattern for single authority
#[account]
pub struct SingleAuthorityAccount {
pub authority: Pubkey, // User who controls this account
pub data: [u8; 32],
}
// Instruction to modify requires authority signature
#[derive(Accounts)]
pub struct ModifyWithAuthority<'info> {
#[account(
mut,
has_one = authority, // Enforces authority check
)]
pub account: Account<'info, SingleAuthorityAccount>,
pub authority: Signer<'info>, // Must sign transaction
}
Multiple Authorities
Rust
// Different authorities for different operations
#[account]
pub struct MultiAuthorityAccount {
pub admin: Pubkey, // Can change settings
pub operator: Pubkey, // Can perform operations
pub withdraw: Pubkey, // Can withdraw funds
pub data: [u8; 64],
}
#[derive(Accounts)]
pub struct AdminOperation<'info> {
#[account(mut, has_one = admin)]
pub account: Account<'info, MultiAuthorityAccount>,
pub admin: Signer<'info>,
}
#[derive(Accounts)]
pub struct OperatorOperation<'info> {
#[account(mut, has_one = operator)]
pub account: Account<'info, MultiAuthorityAccount>,
pub operator: Signer<'info>,
}
#[derive(Accounts)]
pub struct WithdrawOperation<'info> {
#[account(mut, has_one = withdraw)]
pub account: Account<'info, MultiAuthorityAccount>,
pub withdraw: Signer<'info>,
}
Authority Transfer
Rust
// Transferring authority to a new user
pub fn transfer_authority(
ctx: Context<TransferAuthority>,
new_authority: Pubkey,
) -> Result<()> {
let account = &mut ctx.accounts.account;
// Old authority must sign (enforced by Accounts struct)
account.authority = new_authority;
msg!("Authority transferred to: {}", new_authority);
Ok(())
}
#[derive(Accounts)]
pub struct TransferAuthority<'info> {
#[account(mut, has_one = authority)]
pub account: Account<'info, SingleAuthorityAccount>,
pub authority: Signer<'info>, // Current authority signs
/// CHECK: New authority, no signature needed
pub new_authority: AccountInfo<'info>,
}
PDA Authorities
PDAs (Program Derived Addresses) can serve as authorities, enabling program-controlled accounts.
Rust
// PDA as authority pattern
#[account]
pub struct VaultAccount {
pub authority: Pubkey, // PDA, not a user
pub admin: Pubkey, // User who can manage
pub balance: u64,
}
// The PDA authority can "sign" via CPI
pub fn withdraw_from_vault(
ctx: Context<WithdrawFromVault>,
amount: u64,
) -> Result<()> {
// Transfer tokens where vault PDA is the authority
let seeds = &[
b"vault",
ctx.accounts.vault.admin.as_ref(),
&[ctx.bumps.vault_authority],
];
let signer = &[&seeds[..]];
// CPI with PDA signer
transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Transfer {
from: ctx.accounts.vault_token_account.to_account_info(),
to: ctx.accounts.destination.to_account_info(),
authority: ctx.accounts.vault_authority.to_account_info(),
},
signer,
),
amount,
)?;
Ok(())
}
#[derive(Accounts)]
pub struct WithdrawFromVault<'info> {
#[account(
seeds = [b"vault", admin.key().as_ref()],
bump,
)]
pub vault_authority: AccountInfo<'info>, // PDA
pub admin: Signer<'info>, // User who controls the vault
// ... token accounts
}
Common Authority Patterns
1. Upgrade Authority
Programs can have upgrade authority:
TypeScript
import { PublicKey } from "@solana/web3.js";
// Get program's upgrade authority
async function getProgramAuthority(
connection: Connection,
programId: PublicKey,
): Promise<PublicKey | null> {
const programAccount = await connection.getAccountInfo(programId);
if (!programAccount) return null;
// Program accounts point to program data account
// which contains the upgrade authority
const programDataAddress = new PublicKey(programAccount.data.slice(4, 36));
const programData = await connection.getAccountInfo(programDataAddress);
if (!programData) return null;
// Authority is at offset 13 in program data
const authorityOption = programData.data[12];
if (authorityOption === 0) {
return null; // No authority (immutable)
}
return new PublicKey(programData.data.slice(13, 45));
}
2. Mint Authority
Token mints have mint and freeze authorities:
TypeScript
import { getMint } from "@solana/spl-token";
async function getMintAuthorities(
connection: Connection,
mintAddress: PublicKey,
) {
const mint = await getMint(connection, mintAddress);
return {
mintAuthority: mint.mintAuthority, // Can mint new tokens
freezeAuthority: mint.freezeAuthority, // Can freeze accounts
supply: mint.supply,
decimals: mint.decimals,
};
}
// Disable minting forever
import { setAuthority, AuthorityType } from "@solana/spl-token";
async function disableMinting(
connection: Connection,
mint: PublicKey,
currentAuthority: Keypair,
) {
await setAuthority(
connection,
currentAuthority, // Payer
mint, // Mint account
currentAuthority, // Current authority
AuthorityType.MintTokens,
null, // New authority (null = disable)
);
}
3. Delegate Authority
Token accounts can delegate spending:
TypeScript
import { approve, revoke, getAccount } from "@solana/spl-token";
// Approve delegate to spend tokens
async function approveDelegate(
connection: Connection,
tokenAccount: PublicKey,
delegate: PublicKey,
owner: Keypair,
amount: bigint,
) {
await approve(
connection,
owner, // Payer
tokenAccount, // Token account
delegate, // Who can spend
owner, // Owner
amount, // Max amount
);
}
// Check delegation status
async function getDelegation(connection: Connection, tokenAccount: PublicKey) {
const account = await getAccount(connection, tokenAccount);
return {
delegate: account.delegate,
delegatedAmount: account.delegatedAmount,
isDelegate: account.delegate !== null,
};
}
Security Considerations
Common Vulnerabilities
Rust
// VULNERABLE: Missing authority check
pub fn bad_withdraw(ctx: Context<BadWithdraw>, amount: u64) -> Result<()> {
// Anyone can call this!
let vault = &mut ctx.accounts.vault;
vault.balance -= amount;
// Transfer happens...
Ok(())
}
// SECURE: Proper authority check
pub fn good_withdraw(ctx: Context<GoodWithdraw>, amount: u64) -> Result<()> {
// Authority check enforced by Accounts struct
let vault = &mut ctx.accounts.vault;
vault.balance -= amount;
Ok(())
}
#[derive(Accounts)]
pub struct GoodWithdraw<'info> {
#[account(
mut,
has_one = authority, // ← This is critical
)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>, // ← Must sign
}
Authority Best Practices
Rust
// 1. Always use has_one or custom constraints
#[account(has_one = authority)]
pub account: Account<'info, MyAccount>,
// 2. Make authority a Signer
pub authority: Signer<'info>, // Not just AccountInfo
// 3. Consider multisig for high-value operations
#[account]
pub struct MultisigAccount {
pub signers: [Pubkey; 3],
pub threshold: u8, // 2 of 3
}
// 4. Implement authority transfer with two-step process
#[account]
pub struct TwoStepAuthority {
pub authority: Pubkey,
pub pending_authority: Option<Pubkey>,
}
// Step 1: Propose new authority
// Step 2: New authority accepts
// Prevents accidental transfers to wrong address
Checking Authority Client-Side
TypeScript
// Verify authority before sending transaction
async function verifyAuthority(
connection: Connection,
accountAddress: PublicKey,
expectedAuthority: PublicKey,
): Promise<boolean> {
const account = await connection.getAccountInfo(accountAddress);
if (!account) {
throw new Error("Account not found");
}
// Parse authority from account data
// (Exact offset depends on account structure)
const authorityOffset = 8; // After discriminator for Anchor
const authority = new PublicKey(
account.data.slice(authorityOffset, authorityOffset + 32),
);
return authority.equals(expectedAuthority);
}
Owner vs Authority Summary
| Aspect | Owner | Authority |
|---|---|---|
| Stored in | Account metadata | Account data |
| Type | Always a program | Any pubkey (user/PDA) |
| Controls | Data modification rights | Application-level access |
| Set by | Account creation | Program logic |
| Changed by | Current owner program | Program instruction |
| Enforced by | Runtime | Program code |
Key Takeaways
- Owner is always a program - it's who can modify account data
- Authority is a convention - stored in data, enforced by programs
- Signer requirement - authorities should always be Signers
- PDA authorities enable program-controlled accounts
- Security depends on proper checks - always verify authority
Next: Rent & Exemption - Deep dive into rent mechanics.