Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Commit 8edfdb2

Browse files
committed
Add login command
1 parent 491512f commit 8edfdb2

File tree

7 files changed

+142
-7
lines changed

7 files changed

+142
-7
lines changed

.vscode/launch.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"version": "0.2.0",
66
"configurations": [
77
{
8-
"name": ".NET Core Launch (console)",
8+
"name": "help",
99
"type": "coreclr",
1010
"request": "launch",
1111
"preLaunchTask": "build",
@@ -15,6 +15,17 @@
1515
"console": "internalConsole",
1616
"stopAtEntry": false
1717
},
18+
{
19+
"name": "me get",
20+
"type": "coreclr",
21+
"request": "launch",
22+
"preLaunchTask": "build",
23+
"program": "${workspaceFolder}/src/bin/Debug/net6.0/msgraph-cli.dll",
24+
"args": ["me", "get"],
25+
"cwd": "${workspaceFolder}",
26+
"console": "internalConsole",
27+
"stopAtEntry": false
28+
},
1829
{
1930
"name": ".NET Core Attach",
2031
"type": "coreclr",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using Azure.Core;
5+
using Azure.Identity;
6+
using Microsoft.Graph.Cli.Utils;
7+
8+
namespace Microsoft.Graph.Cli.Authentication;
9+
10+
class AuthenticationServiceFactory {
11+
public async Task<IAuthenticationService> GetAuthenticationServiceAsync(AuthenticationStrategy strategy, bool persistToken = false) {
12+
switch (strategy) {
13+
case AuthenticationStrategy.DeviceCode:
14+
return await GetDeviceCodeAuthenticationServiceAsync(persistToken);
15+
default:
16+
throw new InvalidOperationException($"The authentication strategy {strategy} is not supported");
17+
}
18+
19+
}
20+
21+
public async Task<TokenCredential> GetTokenCredentialAsync(AuthenticationStrategy strategy, bool persistToken = false) {
22+
switch (strategy) {
23+
case AuthenticationStrategy.DeviceCode:
24+
return await GetDeviceCodeCredentialAsync(persistToken);
25+
default:
26+
throw new InvalidOperationException($"The authentication strategy {strategy} is not supported");
27+
}
28+
}
29+
30+
private async Task<DeviceCodeAuthenticationService> GetDeviceCodeAuthenticationServiceAsync(bool persistToken = false) {
31+
var credential = await GetDeviceCodeCredentialAsync(persistToken);
32+
return new(credential);
33+
}
34+
35+
private async Task<DeviceCodeCredential> GetDeviceCodeCredentialAsync(bool persistToken) {
36+
DeviceCodeCredentialOptions credOptions = new()
37+
{
38+
ClientId = Constants.ClientId,
39+
TenantId = Constants.TenantId
40+
};
41+
42+
if (persistToken) {
43+
TokenCachePersistenceOptions tokenCacheOptions = new() { Name = Constants.TokenCacheName };
44+
credOptions.TokenCachePersistenceOptions = tokenCacheOptions;
45+
var recordPath = Constants.AuthRecordPath;
46+
47+
if (File.Exists(recordPath))
48+
{
49+
using var authRecordStream = new FileStream(recordPath, FileMode.Open, FileAccess.Read);
50+
var authRecord = await AuthenticationRecord.DeserializeAsync(authRecordStream);
51+
credOptions.AuthenticationRecord = authRecord;
52+
}
53+
}
54+
55+
return new DeviceCodeCredential(credOptions);
56+
}
57+
}
58+
59+
enum AuthenticationStrategy {
60+
DeviceCode
61+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using Azure.Core;
2+
using Azure.Identity;
3+
using Microsoft.Graph.Cli.Utils;
4+
using System.IO;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.Graph.Cli.Authentication;
8+
9+
class DeviceCodeAuthenticationService : IAuthenticationService {
10+
private DeviceCodeCredential credential;
11+
12+
public DeviceCodeAuthenticationService(DeviceCodeCredential credential) {
13+
this.credential = credential;
14+
}
15+
16+
public async Task LoginAsync(string[] scopes) {
17+
var authRecord = await credential.AuthenticateAsync(new TokenRequestContext(scopes));
18+
// Serialize the AuthenticationRecord to disk so that it can be re-used across executions of this initialization code.
19+
using var authRecordStream = new FileStream(Constants.AuthRecordPath, FileMode.OpenOrCreate, FileAccess.Write);
20+
await authRecord.SerializeAsync(authRecordStream);
21+
}
22+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.Threading.Tasks;
2+
3+
namespace Microsoft.Graph.Cli.Authentication;
4+
5+
interface IAuthenticationService {
6+
Task LoginAsync(string[] scopes);
7+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.Graph.Cli.Authentication;
2+
using System.CommandLine;
3+
using System.CommandLine.Invocation;
4+
5+
namespace Microsoft.Graph.Cli.Commands.Authentication;
6+
7+
class LoginCommand
8+
{
9+
private IAuthenticationService authenticationService;
10+
11+
public LoginCommand(IAuthenticationService authenticationService) {
12+
this.authenticationService = authenticationService;
13+
}
14+
15+
public Command Build() {
16+
var loginCommand = new Command("login", "Login and store the session for use in subsequent commands");
17+
var scopes = new Option<string>("--scopes", "The login scopes e.g. User.Read");
18+
scopes.IsRequired = true;
19+
scopes.Arity = ArgumentArity.OneOrMore;
20+
loginCommand.AddOption(scopes);
21+
loginCommand.Handler = CommandHandler.Create<string[]>(async (scopes) =>
22+
{
23+
await this.authenticationService.LoginAsync(scopes);
24+
});
25+
26+
return loginCommand;
27+
}
28+
}

src/Program.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using ApiSdk;
22
using Azure.Identity;
3+
using Microsoft.Graph.Cli.Authentication;
4+
using Microsoft.Graph.Cli.Commands.Authentication;
35
using Microsoft.Graph.Cli.Utils;
46
using Microsoft.Kiota.Authentication.Azure;
57
using Microsoft.Kiota.Http.HttpClientLibrary;
@@ -10,19 +12,21 @@ namespace Microsoft.Graph.Cli
1012
{
1113
class Program {
1214
static async Task<int> Main(string[] args) {
13-
DeviceCodeCredentialOptions credOptions = new()
14-
{
15-
ClientId = Constants.ClientId,
16-
TenantId = Constants.TenantId
17-
};
15+
var authServiceFactory = new AuthenticationServiceFactory();
16+
var authStrategy = AuthenticationStrategy.DeviceCode;
17+
var persistToken = true;
1818

19-
var credential = new DeviceCodeCredential(credOptions);
19+
var credential = await authServiceFactory.GetTokenCredentialAsync(authStrategy, persistToken);
2020
var authProvider = new AzureIdentityAuthenticationProvider(credential);
2121
var core = new HttpClientRequestAdapter(authProvider);
2222
var client = new GraphClient(core);
2323

2424
var rootCommand = client.BuildCommand();
2525
rootCommand.Description = "Microsoft Graph CLI";
26+
27+
var authenticationService = await authServiceFactory.GetAuthenticationServiceAsync(authStrategy, persistToken);
28+
var loginCommand = new LoginCommand(authenticationService);
29+
rootCommand.AddCommand(loginCommand.Build());
2630
return await rootCommand.InvokeAsync(args);
2731
}
2832
}

src/utils/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class Constants
44
{
55
public const string AuthRecordPath = "authRecord";
66

7+
public const string TokenCacheName = "MicrosoftGraph";
8+
79
public const string ClientId = "f645f5f8-2332-496d-9d85-e714b1192f0c";
810

911
public const string TenantId = "39aafea4-2975-4790-9c07-e616c1c35d99";

0 commit comments

Comments
 (0)