From 84dcd7dc18ea5cbee81e954004d64109221ea2b0 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Thu, 25 Sep 2025 15:14:56 -0700 Subject: [PATCH 1/7] deprecate ROPC flow --- apps/confidential/confidential.go | 2 +- apps/public/public.go | 3 +- apps/public/public_test.go | 19 ++++--- apps/tests/devapps/main.go | 4 +- .../tests/devapps/username_password_sample.go | 56 ------------------- apps/tests/integration/integration_test.go | 2 + 6 files changed, 18 insertions(+), 68 deletions(-) delete mode 100644 apps/tests/devapps/username_password_sample.go diff --git a/apps/confidential/confidential.go b/apps/confidential/confidential.go index 549d68ab..4a6ddec8 100644 --- a/apps/confidential/confidential.go +++ b/apps/confidential/confidential.go @@ -621,7 +621,7 @@ type AcquireByUsernamePasswordOption interface { } // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. -// NOTE: this flow is NOT recommended. +// Deprecated: This API will be removed in a future release. Use a more secure flow instead. Follow this guide for migration: https://aka.ms/msal-ropc-migration // // Options: [WithClaims], [WithTenantID] func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { diff --git a/apps/public/public.go b/apps/public/public.go index 7beed261..56cd340a 100644 --- a/apps/public/public.go +++ b/apps/public/public.go @@ -368,9 +368,8 @@ type AcquireByUsernamePasswordOption interface { acquireByUsernamePasswordOption() } +// Deprecated: This API will be removed in a future release. Use a more secure flow instead. Follow this migration guide: https://aka.ms/msal-ropc-migration // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. -// NOTE: this flow is NOT recommended. -// // Options: [WithClaims], [WithTenantID] func (pca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { o := acquireTokenByUsernamePasswordOptions{} diff --git a/apps/public/public_test.go b/apps/public/public_test.go index fa019ca5..291fef6e 100644 --- a/apps/public/public_test.go +++ b/apps/public/public_test.go @@ -116,6 +116,7 @@ func TestAcquireTokenSilentHomeTenantAliases(t *testing.T) { } func TestAcquireTokenSilentWithTenantID(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") tenantA, tenantB := "a", "b" lmo := "login.microsoftonline.com" mockClient := mock.NewClient() @@ -278,6 +279,7 @@ func TestAcquireTokenByDeviceCode(t *testing.T) { } func TestAcquireTokenWithTenantID(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) uuid1 := "00000000-0000-0000-0000-000000000000" @@ -295,7 +297,7 @@ func TestAcquireTokenWithTenantID(t *testing.T) { {authority: host + uuid1, tenant: "organizations", expectError: true}, {authority: host + "consumers", tenant: uuid1, expectError: true}, } { - for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive", "password"} { + for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive"} { t.Run(method, func(t *testing.T) { URL := "" mockClient := mock.NewClient() @@ -306,10 +308,7 @@ func TestAcquireTokenWithTenantID(t *testing.T) { mockClient.AppendResponse(mock.WithBody(mock.GetTenantDiscoveryBody(lmo, test.tenant))) if method == "devicecode" { mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":"...","expires_in":600}`))) - } else if method == "password" { - // user realm metadata - mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`))) - } + } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, mock.GetIDToken(test.tenant, test.authority), "rt", clientInfo, 3600, 0)), mock.WithCallback(func(r *http.Request) { URL = r.URL.String() }), @@ -331,8 +330,6 @@ func TestAcquireTokenWithTenantID(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithTenantID(test.tenant)) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithTenantID(test.tenant), WithOpenURL(fakeBrowserOpenURL)) - case "password": - ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithTenantID(test.tenant)) default: t.Fatalf("test bug: no test for %s", method) } @@ -379,6 +376,7 @@ func TestAcquireTokenWithTenantID(t *testing.T) { } func TestADFSTokenCaching(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") client, err := New("clientID", WithAuthority("https://fake_authority/adfs")) if err != nil { t.Fatal(err) @@ -444,6 +442,7 @@ func TestADFSTokenCaching(t *testing.T) { } func TestWithInstanceDiscovery(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) host := "stack.local" @@ -647,6 +646,9 @@ func TestWithClaims(t *testing.T) { } for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive", "password", "passwordFederated"} { t.Run(method, func(t *testing.T) { + if method == "password" || method == "passwordFederated" { + t.Skip("Test case skipped due to deprecated AcquireTokenByUsernamePassword API usage") + } mockClient := mock.NewClient() if method == "obo" { // TODO: OBO does instance discovery twice before first token request https://github.com/AzureAD/microsoft-authentication-library-for-go/issues/351 @@ -965,6 +967,9 @@ func TestWithAuthenticationScheme(t *testing.T) { }, } { t.Run(testCase.name, func(t *testing.T) { + if testCase.name == "password" { + t.Skip("Test case skipped due to deprecated AcquireTokenByUsernamePassword API usage") + } ctx := context.Background() // get a fresh client to avoid any overflow from other tests diff --git a/apps/tests/devapps/main.go b/apps/tests/devapps/main.go index ca0d12e6..c1e2731b 100644 --- a/apps/tests/devapps/main.go +++ b/apps/tests/devapps/main.go @@ -21,8 +21,8 @@ func main() { acquireByAuthorizationCodePublic() */ } else if exampleType == "3" { - // This sample uses a serialized cache in an ecrypted file on Windows / KeyChain on Mac / KeyRing on Linux - acquireByUsernamePasswordPublic(ctx) + // This sample has been removed due to deprecated AcquireTokenByUsernamePassword API usage + panic("username/password sample has been removed - use a more secure authentication flow") } else if exampleType == "4" { panic("currently not implemented") //acquireByAuthorizationCodeConfidential() diff --git a/apps/tests/devapps/username_password_sample.go b/apps/tests/devapps/username_password_sample.go deleted file mode 100644 index c5a5b709..00000000 --- a/apps/tests/devapps/username_password_sample.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -package main - -import ( - "context" - "fmt" - "log" - - "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" -) - -func acquireByUsernamePasswordPublic(ctx context.Context) { - config := CreateConfig("config.json") - app, err := public.New(config.ClientID, public.WithCache(cacheAccessor), public.WithAuthority(config.Authority)) - if err != nil { - panic(err) - } - - // look in the cache to see if the account to use has been cached - var userAccount public.Account - accounts, err := app.Accounts(ctx) - if err != nil { - panic("failed to read the cache") - } - for _, account := range accounts { - if account.PreferredUsername == config.Username { - userAccount = account - } - } - // found a cached account, now see if an applicable token has been cached - // NOTE: this API conflates error states, i.e. err is non-nil if an applicable token isn't - // cached or if something goes wrong (making the HTTP request, unmarshalling, etc). - result, err := app.AcquireTokenSilent( - context.Background(), - config.Scopes, - public.WithSilentAccount(userAccount), - ) - if err != nil { - // either there's no applicable token in the cache or something failed - log.Println(err) - // either there was no cached account/token or the call to AcquireTokenSilent() failed - // make a new request to AAD - result, err = app.AcquireTokenByUsernamePassword( - context.Background(), - config.Scopes, - config.Username, - config.Password, - ) - if err != nil { - log.Fatal(err) - } - } - fmt.Println("Access token is " + result.AccessToken) -} diff --git a/apps/tests/integration/integration_test.go b/apps/tests/integration/integration_test.go index d1e93e4f..d5262556 100644 --- a/apps/tests/integration/integration_test.go +++ b/apps/tests/integration/integration_test.go @@ -209,6 +209,7 @@ func testUser(ctx context.Context, desc string, lc *labClient, query url.Values) } func TestUsernamePassword(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") if testing.Short() { t.Skip("skipping integration test") } @@ -406,6 +407,7 @@ func TestOnBehalfOf(t *testing.T) { } func TestRemoveAccount(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") if testing.Short() { t.Skip("skipping integration test") } From a646b94b5c8d50304116d79d990243d3ef436a29 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Thu, 25 Sep 2025 15:43:27 -0700 Subject: [PATCH 2/7] updates --- apps/public/public_test.go | 38 +++----------------------------------- apps/tests/devapps/main.go | 2 +- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/apps/public/public_test.go b/apps/public/public_test.go index 291fef6e..39fe6e44 100644 --- a/apps/public/public_test.go +++ b/apps/public/public_test.go @@ -279,7 +279,6 @@ func TestAcquireTokenByDeviceCode(t *testing.T) { } func TestAcquireTokenWithTenantID(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) uuid1 := "00000000-0000-0000-0000-000000000000" @@ -442,7 +441,6 @@ func TestADFSTokenCaching(t *testing.T) { } func TestWithInstanceDiscovery(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) host := "stack.local" @@ -452,17 +450,14 @@ func TestWithInstanceDiscovery(t *testing.T) { "adfs", "98b8267d-e97f-426e-8b3f-7956511fd63f", } { - for _, method := range []string{"authcode", "devicecode", "interactive", "password"} { + for _, method := range []string{"authcode", "devicecode", "interactive"} { t.Run(method, func(t *testing.T) { authority := stackurl + tenant mockClient := mock.NewClient() mockClient.AppendResponse(mock.WithBody(mock.GetTenantDiscoveryBody(host, tenant))) if method == "devicecode" { mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":"...","expires_in":600}`))) - } else if method == "password" && tenant != "adfs" { - // user realm metadata, which is not requested when AuthorityType is ADFS - mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`))) - } + } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, mock.GetIDToken(tenant, authority), "rt", clientInfo, 3600, 0)), ) @@ -481,8 +476,6 @@ func TestWithInstanceDiscovery(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithOpenURL(fakeBrowserOpenURL)) - case "password": - ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password") default: t.Fatal("test bug: no test for " + method) } @@ -644,11 +637,8 @@ func TestWithClaims(t *testing.T) { t.Fatal(diff) } } - for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive", "password", "passwordFederated"} { + for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive"} { t.Run(method, func(t *testing.T) { - if method == "password" || method == "passwordFederated" { - t.Skip("Test case skipped due to deprecated AcquireTokenByUsernamePassword API usage") - } mockClient := mock.NewClient() if method == "obo" { // TODO: OBO does instance discovery twice before first token request https://github.com/AzureAD/microsoft-authentication-library-for-go/issues/351 @@ -658,10 +648,6 @@ func TestWithClaims(t *testing.T) { switch method { case "devicecode": mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":".","expires_in":600}`))) - case "password": - mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":".","cloud_instance_name":".","domain_name":"."}`))) - case "passwordFederated": - mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Federated","cloud_audience_urn":".","cloud_instance_name":".","domain_name":".","federation_protocol":".","federation_metadata_url":"."}`))) } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0)), @@ -695,11 +681,6 @@ func TestWithClaims(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithClaims(test.claims)) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithClaims(test.claims), WithOpenURL(fakeBrowserOpenURL)) - case "password": - ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithClaims(test.claims)) - case "passwordFederated": - client.base.Token.WSTrust = fake.WSTrust{SamlTokenInfo: wstrust.SamlTokenInfo{AssertionType: "urn:ietf:params:oauth:grant-type:saml1_1-bearer"}} - ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithClaims(test.claims)) default: t.Fatalf("test bug: no test for %s", method) } @@ -957,19 +938,8 @@ func TestWithAuthenticationScheme(t *testing.T) { mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0), }, }, - { - name: "password", - responses: [][]byte{ - mock.GetTenantDiscoveryBody(lmo, tenant), - []byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`), - mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0), - }, - }, } { t.Run(testCase.name, func(t *testing.T) { - if testCase.name == "password" { - t.Skip("Test case skipped due to deprecated AcquireTokenByUsernamePassword API usage") - } ctx := context.Background() // get a fresh client to avoid any overflow from other tests @@ -991,8 +961,6 @@ func TestWithAuthenticationScheme(t *testing.T) { switch testCase.name { case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithAuthenticationScheme(authScheme), WithOpenURL(fakeBrowserOpenURL)) - case "password": - ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithAuthenticationScheme(authScheme)) default: t.Fatalf("test bug: no test for %s", testCase.name) } diff --git a/apps/tests/devapps/main.go b/apps/tests/devapps/main.go index c1e2731b..26b9cdc2 100644 --- a/apps/tests/devapps/main.go +++ b/apps/tests/devapps/main.go @@ -22,7 +22,7 @@ func main() { */ } else if exampleType == "3" { // This sample has been removed due to deprecated AcquireTokenByUsernamePassword API usage - panic("username/password sample has been removed - use a more secure authentication flow") + panic("username/password sample has been removed due to deprecation of the api - use a more secure authentication flow") } else if exampleType == "4" { panic("currently not implemented") //acquireByAuthorizationCodeConfidential() From a008e20c11aa04d03789066d68ba1fa84a7dd8d5 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Thu, 25 Sep 2025 15:50:53 -0700 Subject: [PATCH 3/7] address comments --- apps/public/public.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/public/public.go b/apps/public/public.go index 56cd340a..797c086c 100644 --- a/apps/public/public.go +++ b/apps/public/public.go @@ -369,6 +369,7 @@ type AcquireByUsernamePasswordOption interface { } // Deprecated: This API will be removed in a future release. Use a more secure flow instead. Follow this migration guide: https://aka.ms/msal-ropc-migration +// // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. // Options: [WithClaims], [WithTenantID] func (pca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { From 076aec097b6b4cc8e41e814b7111aabc35751ee8 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Fri, 26 Sep 2025 17:09:16 -0700 Subject: [PATCH 4/7] reenable tests and remoce cca flow deprecation --- apps/confidential/confidential.go | 1 - apps/public/public_test.go | 42 +++++++++++--- apps/tests/devapps/main.go | 4 +- .../tests/devapps/username_password_sample.go | 56 +++++++++++++++++++ apps/tests/integration/integration_test.go | 2 - 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 apps/tests/devapps/username_password_sample.go diff --git a/apps/confidential/confidential.go b/apps/confidential/confidential.go index 4a6ddec8..f36f36cd 100644 --- a/apps/confidential/confidential.go +++ b/apps/confidential/confidential.go @@ -621,7 +621,6 @@ type AcquireByUsernamePasswordOption interface { } // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. -// Deprecated: This API will be removed in a future release. Use a more secure flow instead. Follow this guide for migration: https://aka.ms/msal-ropc-migration // // Options: [WithClaims], [WithTenantID] func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { diff --git a/apps/public/public_test.go b/apps/public/public_test.go index 39fe6e44..78b30c57 100644 --- a/apps/public/public_test.go +++ b/apps/public/public_test.go @@ -296,7 +296,7 @@ func TestAcquireTokenWithTenantID(t *testing.T) { {authority: host + uuid1, tenant: "organizations", expectError: true}, {authority: host + "consumers", tenant: uuid1, expectError: true}, } { - for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive"} { + for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive", "password"} { t.Run(method, func(t *testing.T) { URL := "" mockClient := mock.NewClient() @@ -307,7 +307,10 @@ func TestAcquireTokenWithTenantID(t *testing.T) { mockClient.AppendResponse(mock.WithBody(mock.GetTenantDiscoveryBody(lmo, test.tenant))) if method == "devicecode" { mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":"...","expires_in":600}`))) - } + } else if method == "password" { + // user realm metadata + mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`))) + } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, mock.GetIDToken(test.tenant, test.authority), "rt", clientInfo, 3600, 0)), mock.WithCallback(func(r *http.Request) { URL = r.URL.String() }), @@ -329,6 +332,8 @@ func TestAcquireTokenWithTenantID(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithTenantID(test.tenant)) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithTenantID(test.tenant), WithOpenURL(fakeBrowserOpenURL)) + case "password": + ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithTenantID(test.tenant)) default: t.Fatalf("test bug: no test for %s", method) } @@ -375,7 +380,6 @@ func TestAcquireTokenWithTenantID(t *testing.T) { } func TestADFSTokenCaching(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") client, err := New("clientID", WithAuthority("https://fake_authority/adfs")) if err != nil { t.Fatal(err) @@ -441,6 +445,7 @@ func TestADFSTokenCaching(t *testing.T) { } func TestWithInstanceDiscovery(t *testing.T) { + t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) host := "stack.local" @@ -450,14 +455,17 @@ func TestWithInstanceDiscovery(t *testing.T) { "adfs", "98b8267d-e97f-426e-8b3f-7956511fd63f", } { - for _, method := range []string{"authcode", "devicecode", "interactive"} { + for _, method := range []string{"authcode", "devicecode", "interactive", "password"} { t.Run(method, func(t *testing.T) { authority := stackurl + tenant mockClient := mock.NewClient() mockClient.AppendResponse(mock.WithBody(mock.GetTenantDiscoveryBody(host, tenant))) if method == "devicecode" { mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":"...","expires_in":600}`))) - } + } else if method == "password" && tenant != "adfs" { + // user realm metadata, which is not requested when AuthorityType is ADFS + mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`))) + } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, mock.GetIDToken(tenant, authority), "rt", clientInfo, 3600, 0)), ) @@ -476,6 +484,8 @@ func TestWithInstanceDiscovery(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithOpenURL(fakeBrowserOpenURL)) + case "password": + ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password") default: t.Fatal("test bug: no test for " + method) } @@ -588,7 +598,6 @@ func TestWithCache(t *testing.T) { func TestWithClaims(t *testing.T) { clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) - lmo, tenant := "login.microsoftonline.com", "tenant" authority := fmt.Sprintf(authorityFmt, lmo, tenant) accessToken, idToken, refreshToken := "at", mock.GetIDToken(tenant, lmo), "rt" for _, test := range []struct { @@ -637,7 +646,7 @@ func TestWithClaims(t *testing.T) { t.Fatal(diff) } } - for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive"} { + for _, method := range []string{"authcode", "authcodeURL", "devicecode", "interactive", "password", "passwordFederated"} { t.Run(method, func(t *testing.T) { mockClient := mock.NewClient() if method == "obo" { @@ -648,6 +657,10 @@ func TestWithClaims(t *testing.T) { switch method { case "devicecode": mockClient.AppendResponse(mock.WithBody([]byte(`{"device_code":".","expires_in":600}`))) + case "password": + mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Managed","cloud_audience_urn":".","cloud_instance_name":".","domain_name":"."}`))) + case "passwordFederated": + mockClient.AppendResponse(mock.WithBody([]byte(`{"account_type":"Federated","cloud_audience_urn":".","cloud_instance_name":".","domain_name":".","federation_protocol":".","federation_metadata_url":"."}`))) } mockClient.AppendResponse( mock.WithBody(mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0)), @@ -681,6 +694,11 @@ func TestWithClaims(t *testing.T) { dc, err = client.AcquireTokenByDeviceCode(ctx, tokenScope, WithClaims(test.claims)) case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithClaims(test.claims), WithOpenURL(fakeBrowserOpenURL)) + case "password": + ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithClaims(test.claims)) + case "passwordFederated": + client.base.Token.WSTrust = fake.WSTrust{SamlTokenInfo: wstrust.SamlTokenInfo{AssertionType: "urn:ietf:params:oauth:grant-type:saml1_1-bearer"}} + ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithClaims(test.claims)) default: t.Fatalf("test bug: no test for %s", method) } @@ -938,6 +956,14 @@ func TestWithAuthenticationScheme(t *testing.T) { mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0), }, }, + { + name: "password", + responses: [][]byte{ + mock.GetTenantDiscoveryBody(lmo, tenant), + []byte(`{"account_type":"Managed","cloud_audience_urn":"urn","cloud_instance_name":"...","domain_name":"..."}`), + mock.GetAccessTokenBody(accessToken, idToken, refreshToken, clientInfo, 3600, 0), + }, + }, } { t.Run(testCase.name, func(t *testing.T) { ctx := context.Background() @@ -961,6 +987,8 @@ func TestWithAuthenticationScheme(t *testing.T) { switch testCase.name { case "interactive": ar, err = client.AcquireTokenInteractive(ctx, tokenScope, WithAuthenticationScheme(authScheme), WithOpenURL(fakeBrowserOpenURL)) + case "password": + ar, err = client.AcquireTokenByUsernamePassword(ctx, tokenScope, "username", "password", WithAuthenticationScheme(authScheme)) default: t.Fatalf("test bug: no test for %s", testCase.name) } diff --git a/apps/tests/devapps/main.go b/apps/tests/devapps/main.go index 26b9cdc2..ca0d12e6 100644 --- a/apps/tests/devapps/main.go +++ b/apps/tests/devapps/main.go @@ -21,8 +21,8 @@ func main() { acquireByAuthorizationCodePublic() */ } else if exampleType == "3" { - // This sample has been removed due to deprecated AcquireTokenByUsernamePassword API usage - panic("username/password sample has been removed due to deprecation of the api - use a more secure authentication flow") + // This sample uses a serialized cache in an ecrypted file on Windows / KeyChain on Mac / KeyRing on Linux + acquireByUsernamePasswordPublic(ctx) } else if exampleType == "4" { panic("currently not implemented") //acquireByAuthorizationCodeConfidential() diff --git a/apps/tests/devapps/username_password_sample.go b/apps/tests/devapps/username_password_sample.go new file mode 100644 index 00000000..c5a5b709 --- /dev/null +++ b/apps/tests/devapps/username_password_sample.go @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package main + +import ( + "context" + "fmt" + "log" + + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/public" +) + +func acquireByUsernamePasswordPublic(ctx context.Context) { + config := CreateConfig("config.json") + app, err := public.New(config.ClientID, public.WithCache(cacheAccessor), public.WithAuthority(config.Authority)) + if err != nil { + panic(err) + } + + // look in the cache to see if the account to use has been cached + var userAccount public.Account + accounts, err := app.Accounts(ctx) + if err != nil { + panic("failed to read the cache") + } + for _, account := range accounts { + if account.PreferredUsername == config.Username { + userAccount = account + } + } + // found a cached account, now see if an applicable token has been cached + // NOTE: this API conflates error states, i.e. err is non-nil if an applicable token isn't + // cached or if something goes wrong (making the HTTP request, unmarshalling, etc). + result, err := app.AcquireTokenSilent( + context.Background(), + config.Scopes, + public.WithSilentAccount(userAccount), + ) + if err != nil { + // either there's no applicable token in the cache or something failed + log.Println(err) + // either there was no cached account/token or the call to AcquireTokenSilent() failed + // make a new request to AAD + result, err = app.AcquireTokenByUsernamePassword( + context.Background(), + config.Scopes, + config.Username, + config.Password, + ) + if err != nil { + log.Fatal(err) + } + } + fmt.Println("Access token is " + result.AccessToken) +} diff --git a/apps/tests/integration/integration_test.go b/apps/tests/integration/integration_test.go index d5262556..d1e93e4f 100644 --- a/apps/tests/integration/integration_test.go +++ b/apps/tests/integration/integration_test.go @@ -209,7 +209,6 @@ func testUser(ctx context.Context, desc string, lc *labClient, query url.Values) } func TestUsernamePassword(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") if testing.Short() { t.Skip("skipping integration test") } @@ -407,7 +406,6 @@ func TestOnBehalfOf(t *testing.T) { } func TestRemoveAccount(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") if testing.Short() { t.Skip("skipping integration test") } From 05b11140ff5f21fcdf76a7ba0c45ef7ebe502674 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Fri, 26 Sep 2025 17:13:21 -0700 Subject: [PATCH 5/7] fix --- apps/public/public_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/public/public_test.go b/apps/public/public_test.go index 78b30c57..fa019ca5 100644 --- a/apps/public/public_test.go +++ b/apps/public/public_test.go @@ -116,7 +116,6 @@ func TestAcquireTokenSilentHomeTenantAliases(t *testing.T) { } func TestAcquireTokenSilentWithTenantID(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") tenantA, tenantB := "a", "b" lmo := "login.microsoftonline.com" mockClient := mock.NewClient() @@ -445,7 +444,6 @@ func TestADFSTokenCaching(t *testing.T) { } func TestWithInstanceDiscovery(t *testing.T) { - t.Skip("Test skipped due to deprecated AcquireTokenByUsernamePassword API usage") accessToken := "*" clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) host := "stack.local" @@ -598,6 +596,7 @@ func TestWithCache(t *testing.T) { func TestWithClaims(t *testing.T) { clientInfo := base64.RawStdEncoding.EncodeToString([]byte(`{"uid":"uid","utid":"utid"}`)) + lmo, tenant := "login.microsoftonline.com", "tenant" authority := fmt.Sprintf(authorityFmt, lmo, tenant) accessToken, idToken, refreshToken := "at", mock.GetIDToken(tenant, lmo), "rt" for _, test := range []struct { From bd3ef768a6d6306ccca3097edc7cc192f8af12fc Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Fri, 26 Sep 2025 17:21:40 -0700 Subject: [PATCH 6/7] edit --- apps/confidential/confidential.go | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/confidential/confidential.go b/apps/confidential/confidential.go index f36f36cd..a0345973 100644 --- a/apps/confidential/confidential.go +++ b/apps/confidential/confidential.go @@ -621,6 +621,7 @@ type AcquireByUsernamePasswordOption interface { } // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. +// NOTE: this flow is NOT recommended // // Options: [WithClaims], [WithTenantID] func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) { From bd99886e361fab3dcb4c56fc196276cbcb024c62 Mon Sep 17 00:00:00 2001 From: Ugonna Akali Date: Mon, 29 Sep 2025 07:14:54 -0700 Subject: [PATCH 7/7] add period --- apps/confidential/confidential.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/confidential/confidential.go b/apps/confidential/confidential.go index a0345973..549d68ab 100644 --- a/apps/confidential/confidential.go +++ b/apps/confidential/confidential.go @@ -621,7 +621,7 @@ type AcquireByUsernamePasswordOption interface { } // AcquireTokenByUsernamePassword acquires a security token from the authority, via Username/Password Authentication. -// NOTE: this flow is NOT recommended +// NOTE: this flow is NOT recommended. // // Options: [WithClaims], [WithTenantID] func (cca Client) AcquireTokenByUsernamePassword(ctx context.Context, scopes []string, username, password string, opts ...AcquireByUsernamePasswordOption) (AuthResult, error) {