This is a simple matter of database's, by default, being isolated from other DB's for security purposes. Prior to SQL Server 2005, this issue could be resolved by enabling the Cross-Database Ownership Chaining feature. However, that option is rather wide-open as it does not allow for tailoring who can make use of it. Starting with SQL Server 2005, Module Signing was introduced, and it provides a fine-grained security mechanism for solving this problem, and many others.
I have a few examples on here (DBA.SE) already showing how to do this, such as:
Permissions in triggers when using cross database certificates
That one involves triggers, so I simplified it for this question, as shown below:
CLEANUP
USE [master];
GO
IF EXISTS (SELECT 1 FROM [sys].[databases] WHERE [name] = N'DatabaseA')
BEGIN
PRINT 'Dropping [DatabaseA] DB...';
ALTER DATABASE [DatabaseA] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [DatabaseA] SET ONLINE;
DROP DATABASE [DatabaseA];
END;
IF EXISTS (SELECT 1 FROM [sys].[databases] WHERE [name] = N'DatabaseB')
BEGIN
PRINT 'Dropping [DatabaseB] DB...';
ALTER DATABASE [DatabaseB] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [DatabaseB] SET ONLINE;
DROP DATABASE [DatabaseB];
END;
IF (SUSER_ID(N'JohnnyLunchbucket') IS NOT NULL)
BEGIN
PRINT 'Dropping [JohnnyLunchbucket] Login...';
DROP LOGIN [JohnnyLunchbucket];
END;
SETUP
USE [master];
EXECUTE AS LOGIN = N'sa';
PRINT 'Creating databases...';
CREATE DATABASE [DatabaseA] COLLATE Latin1_General_100_CI_AS_SC;
CREATE DATABASE [DatabaseB] COLLATE Latin1_General_100_CI_AS_SC;
REVERT;
GO
-- Default for both options should be OFF, but just to be sure:
ALTER DATABASE [DatabaseA] SET DB_CHAINING OFF, TRUSTWORTHY OFF, RECOVERY SIMPLE;
ALTER DATABASE [DatabaseB] SET DB_CHAINING OFF, TRUSTWORTHY OFF, RECOVERY SIMPLE;
GO
CREATE LOGIN [JohnnyLunchbucket] WITH PASSWORD = 'OhSoSecure;)';
USE [DatabaseA];
CREATE USER [JohnnyLunchbucket] FOR LOGIN [JohnnyLunchbucket];
GO
CREATE PROCEDURE dbo.SelectFromDatabaseB
AS
SET NOCOUNT ON;
SELECT *
FROM [DatabaseB].dbo.SomeTable;
GO
GRANT EXECUTE ON dbo.SelectFromDatabaseB TO [JohnnyLunchbucket];
GO
USE [DatabaseB];
CREATE TABLE dbo.SomeTable (Col1 INT);
INSERT INTO dbo.SomeTable ([Col1]) VALUES (111);
GO
TEST 1 (no access)
USE [DatabaseA];
EXECUTE AS LOGIN = N'JohnnyLunchbucket';
SELECT * FROM [DatabaseB].dbo.SomeTable;
/*
Msg 916, Level 14, State 1, Line XXXXX
The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context.
*/
EXECUTE dbo.[SelectFromDatabaseB];
/*
Msg 916, Level 14, State 1, Procedure dbo.SelectFromDatabaseB, Line XXXXX [Batch Start Line YYYYY]
The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context.
*/
REVERT;
ADD MODULE SIGNING
CREATE CERTIFICATE [PermissionsCert]
AUTHORIZATION [dbo]
ENCRYPTION BY PASSWORD = 'WeakPassword'
WITH SUBJECT = 'Used to test granting permissions to code',
EXPIRY_DATE = '2099-12-31';
ADD SIGNATURE TO [dbo].[SelectFromDatabaseB]
BY CERTIFICATE [PermissionsCert]
WITH PASSWORD = 'WeakPassword';
-- Copy Certificate (public key only) to DatabaseB
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'USE [DatabaseB];
CREATE CERTIFICATE [PermissionsCert] AUTHORIZATION [dbo] FROM BINARY = '
+ CONVERT(NVARCHAR(MAX), CERTENCODED(CERT_ID(N'PermissionsCert')), 1)
+ N';'
EXEC (@SQL);
USE [DatabaseB];
CREATE USER [PermissionsUser] FROM CERTIFICATE [PermissionsCert];
GRANT SELECT ON [dbo].[SomeTable] TO [PermissionsUser];
GO
TEST 2 (access granted, but only via signed module)
USE [DatabaseA];
EXECUTE AS LOGIN = N'JohnnyLunchbucket';
SELECT * FROM [DatabaseB].dbo.SomeTable;
/*
Msg 916, Level 14, State 1, Line XXXXX
The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context.
*/
EXECUTE dbo.[SelectFromDatabaseB];
-- 111
REVERT;
Only the signed module(s) have the access granted to the Logins and/or Users created from the Certificate. And users only have access to the modules that you grant them access to.