OAuth 2.0 | Blog | Limilabs https://www.limilabs.com/blog Using Limilabs .net components Mon, 07 Oct 2024 10:18:02 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.2 OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP https://www.limilabs.com/blog/oauth2-client-credential-flow-office365-exchange-imap-pop3-smtp Fri, 08 Jul 2022 10:23:20 +0000 https://www.limilabs.com/blog/?p=6211 In this series:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP This article shows how to implement OAuth 2.0 client credential flow to access Office365 via IMAP, POP3 […]

The post OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
In this series:

 

This article shows how to implement OAuth 2.0 client credential flow to access Office365 via IMAP, POP3 using Mail.dll .net email client. This flow is particularly useful for daemon/service apps that need to monitor certain mailboxes, without any user interaction.

Make sure IMAP/POP3 is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

Add permissions to your application in the API permissions / Add a permission wizard:

Select APIs my organization uses and search for Office 365 Exchange Online:

…then click Application permissions:

For POP access, choose the POP.AccessAsApp permission.
For IMAP access, choose the IMAP.AccessAsApp permission.
For SMTP access, choose the SMTP.SendAsApp permission.

Remember to Grant admin consent:

Create an application secret in Certificates & secrets panel by clicking ‘New client secret’ button:

Note the secret value as it is shown only during creation.

Use Windows PowerShell on your machine to Register service principals in Exchange.

Set execution policy first:

Set-ExecutionPolicy RemoteSigned

Install ExchangeOnlineManagement module:

Install-Module -Name ExchangeOnlineManagement 
Import-Module ExchangeOnlineManagement 

Connect and log-in as an administrator (you’ll be prompted for password):

Connect-ExchangeOnline
 -UserPrincipalName your-admin-account@your-domain.onmicrosoft.com

For Exchange running in hybrid mode log-in using following code:

$lc = Get-Credential
Connect-ExchangeOnline -Credential $lc

Create service principal

New-ServicePrincipal
 -AppId <APPLICATION_ID>
 -ServiceId <OBJECT_ID> 
 [-Organization <ORGANIZATION_ID>]

You can find ApplicationId and ObjectId in Enterprise applications in your application’s Overview panel:

Make sure you use the Object ID from the Enterprise Application

Do not use the value from the App Registration screen. 

In our case:

New-ServicePrincipal
 -AppId 061851f7-08c0-40bf-99c1-ebd489c11f16
 -ServiceId 4352fc11-5c2f-4b0b-af40-447ff10664e8

Note: If you still get an error running the New-ServicePrincipal cmdlet after you perform these steps, it is likely due to the fact that the user doesn’t have enough permissions in Exchange online to perform the operation. By default this cmdlet is available to users assigned the Role Management role

Add permissions to a specific mailbox:

Add-MailboxPermission
 -Identity "<USER@your-domain.onmicrosoft.com>"
 -User <OBJECT_ID>
 -AccessRights FullAccess

In our case:

Add-MailboxPermission
 -Identity "AdeleV@your-domain.onmicrosoft.com"
 -User 4352fc11-5c2f-4b0b-af40-447ff10664e8
 -AccessRights FullAccess

Shared mailboxes

You need to use Add-MailboxPermission for every shared mailbox you need access to:

Add-MailboxPermission
 -Identity "shared@your-domain.onmicrosoft.com"
 -User <OBJECT_ID>
 -AccessRights FullAccess

Let’s code

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

// C#

string clientId = "Application (client) ID";    // 061851f7-...
string tenantId = "Directory (tenant) ID";
string clientSecret = "Client secret value";

string userName = "Username/email for mailbox";    // AdeleV@...

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .WithClientSecret(clientSecret)
    .Build();

string[] scopes = new string[] { 
    "https://outlook.office365.com/.default" 
};
' VB.NET

Dim clientId As String = "Application (client) ID" ' 061851f7-...
Dim tenantId As String = "Directory (tenant) ID"
Dim clientSecret As String = "Client secret value"

Dim userName As String = "Username/email for mailbox"  'AdeleV@...

Dim app = ConfidentialClientApplicationBuilder.Create(clientId) _
    .WithTenantId(tenantId) _
    .WithClientSecret(clientSecret) _
    .Build()

Dim scopes As String() = New String() { _
    "https://outlook.office365.com/.default" _
}

Now acquire an access token:

// C#

var result = await app.AcquireTokenForClient(scopes)
    .ExecuteAsync();

string accessToken = result.AccessToken;
' VB.NET

Dim result = Await app.AcquireTokenForClient(scopes).ExecuteAsync()
Dim accessToken As String = result.AccessToken

Finally you can connect using IMAP/POP3, authenticate and download user’s emails:

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

   client.Close();
} 
' VB.NET

Using client As Imap = New Imap()
    client.ConnectSSL("outlook.office365.com")
    client.LoginOAUTH2(userName, accessToken)

    client.SelectInbox()

    Dim uids As List(Of Long) = imap.Search(Flag.Unseen)
    For Each uid As Long In uids
        Dim email As IMail = New MailBuilder() _
            .CreateFromEml(imap.GetMessageByUID(uid))
        Dim subject As String = email.Subject
    Next

    client.Close()
End Using

SMTP

Microsoft started supporting client credential flow and SMTP recently.

SMTP requires SMTP.SendAsApp permission added to your AD application.

All other OAuth flows (webdesktoppassword grantdevice) support SMTP client access as well.

For SMTP non-OAuth2 access:

SMTP AUTH will still be available when Basic authentication is permanently disabled on October 1, 2022.” (https://docs.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online)

However Microsoft disables SMTP AUTH in all tenants in which it’s not being used.

Here’s how to enable SMTP AUTH:
https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission

Troubleshooting

1. Start with PowerShell commands:

Get-ServicePrincipal
Get-MailboxPermission -Identity "AdeleV@your-domain.onmicrosoft.com"

You should see following results:

Make sure the ServiceId is the same as the Object ID on the Enterprise Application screen (do not use the value from the App Registration screen)

Make sure the AppId is the same as the Application ID on the Enterprise Application screen

2. Check if you can connect to this account using IMAP and regular interactive flow:

https://www.limilabs.com/blog/office-365-oauth-2-0-imap-pop3-email-client-connectivity-tools

This proves you have IMAP access properly configured.

3. Check if you added correct permissions and have granted Admin consent for your domain.

4. Usually people use incorrect client/tenant ids/secrets – double check every single value you enter (also for additional spaces).

5. You may need to wait 20-30 minutes for some changes to take effect (it really may take this long!).

Additional links

https://docs.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2?view=exchange-ps#install-and-maintain-the-exo-v2-module
https://docs.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#use-client-credentials-grant-flow-to-authenticate-imap-and-pop-connections


Get Mail.dll

The post OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP https://www.limilabs.com/blog/oauth2-web-flow-office365-exchange-imap-pop3-smtp Wed, 30 Mar 2022 10:04:28 +0000 https://www.limilabs.com/blog/?p=6049 In this series:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP This article shows how to implement OAuth 2.0 web flow to access Office365 via IMAP, POP3 or […]

The post OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
In this series:

 

This article shows how to implement OAuth 2.0 web flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .net email client.

Make sure IMAP/POP3/SMTP is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

RedirectUri

Add an authentication redirect uri to your application:

Then you need to apply correct API permissions and grant the admin consent for your domain.

In the API permissions / Add a permission wizard, select Microsoft Graph and then Delegated permissions to find the following permission scopes listed:

  • offline_access
  • email
  • IMAP.AccessAsUser.All
  • POP.AccessAsUser.All
  • SMTP.Send

Remember to Grant admin consent:

Create an app secret and remember its value:

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

string clientId = "Application (client) ID";
string tenantId = "Directory (tenant) ID";
string clientSecret = "Client secret value";

// for @outlook.com/@hotmail accounts instead of setting .WithTenantId use:
// .WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .WithClientSecret(clientSecret)
    .WithRedirectUri("http://localhost/myapp/")
    .Build();
// This allows saving access/refresh tokens to some storage
TokenCacheHelper.EnableSerialization(app.UserTokenCache);

var scopes = new string[] 
{
    "offline_access",
    "email",
    "https://outlook.office.com/IMAP.AccessAsUser.All",
    "https://outlook.office.com/POP.AccessAsUser.All",
    "https://outlook.office.com/SMTP.Send",
};

In addition, you should request offline_access scope. When a user approves the offline_access scope, your app can receive refresh tokens from the Microsoft identity platform token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.

Now try finding account by an identifier (it will be null on first access) in MSAL cache:

string userName;
string accessToken;

string identifier = null;

var account = await app.GetAccountAsync(identifier);

try
{
    AuthenticationResult refresh = await app
        .AcquireTokenSilent(scopes, account)
        .WithForceRefresh(true)
        .ExecuteAsync();

    userName = refresh.Account.Username;
    accessToken = refresh.AccessToken;

}
catch (MsalUiRequiredException e)
{
    // no token cache entry - perform authentication:

    Uri msUri = await app
        .GetAuthorizationRequestUrl(scopes)
        .ExecuteAsync();

    // Add a redirect code to the above 
    // Microsoft authentication uri and end this request.
}

On the first run user will be redirected to the msUri and will see a Microsoft login screen, with option to log-in, using a known account and granting access to the app (if needed):

After successful authentication Microsoft will redirect user’s browser back to your application – to the app’s RedirectUri (in our case http://localhost/MyApp/):

http://localhost/myapp/?code=0.Aa…AA&client_info=ey…I0In0&session_state=4dd….4488c8#

Controller responsible for handling this request should retrieve code parameter

string code = "get from url after redirect";

AuthenticationResult result = await app
    .AcquireTokenByAuthorizationCode(scopes, code)
    .ExecuteAsync();

string identifier = result.Account.HomeAccountId.Identifier;
string userName = result.Account.Username;
string accessToken = result.AccessToken;

Finally you can connect using IMAP/POP3/SMTP, authenticate and download user’s emails:

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

    client.Close();
} 

Any organization and personal accounts

To access accounts from any organization and personal accounts as well, you need to specify correct account types when you create the App in your AD:

Additionally you need to use:    

    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )

instead of

    .WithTenantId(tenantId)

when creating the app:

var app = ConfidentialClientApplicationBuilder
    .Create(clientId)
    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )
    .WithClientSecret(clientSecret)
    .WithRedirectUri("http://localhost/myapp/")
    .Build();

Token serialization

Below is a simple implementation that saves MSAL token cache to file:

static class TokenCacheHelper
{
    public static void EnableSerialization(ITokenCache tokenCache)
    {
        tokenCache.SetBeforeAccess(BeforeAccessNotification);
        tokenCache.SetAfterAccess(AfterAccessNotification);
    }

    private static readonly string _fileName = "msalcache.bin3";

    private static readonly object _fileLock = new object();


    private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
    {
        lock (_fileLock)
        {
            byte[] data = null;
            if (File.Exists(_fileName))
                data = File.ReadAllBytes(_fileName);
            args.TokenCache.DeserializeMsalV3(data);
        }
    }

    private static void AfterAccessNotification(TokenCacheNotificationArgs args)
    {
        if (args.HasStateChanged)
        {
            lock (_fileLock)
            {
                byte[] data = args.TokenCache.SerializeMsalV3();
                File.WriteAllBytes(_fileName, data);
            }
        }
    }
};

Please note that most likely you should store this cache in an encrypted form in some kind of a database.
Consider using MSAL token serialization implementations available here:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization


Get Mail.dll

The post OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP https://www.limilabs.com/blog/oauth2-device-flow-office365-exchange-imap-pop3-smtp Mon, 28 Mar 2022 13:18:36 +0000 https://www.limilabs.com/blog/?p=5988 In this series:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP This article shows how to implement OAuth 2.0 device flow to access Office365 via IMAP, POP3 or […]

The post OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
In this series:

 

This article shows how to implement OAuth 2.0 device flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .net email client.

Device flow allows operator/administrator to authenticate your application on a different machine than your application is installed.

Make sure IMAP/POP3/SMTP is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

Then you need to apply correct API permissions and grant the admin consent for your domain.

In the API permissions / Add a permission wizard, select Microsoft Graph and then Delegated permissions to find the following permission scopes listed:

  • offline_access
  • email
  • IMAP.AccessAsUser.All
  • POP.AccessAsUser.All
  • SMTP.Send

Remember to Grant admin consent:

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

string clientId = "Application (client) ID";
string tenantId = "Directory (tenant) ID";

IPublicClientApplication app = PublicClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .Build();
// This allows saving access/refresh tokens to some storage
TokenCacheHelper.EnableSerialization(app.UserTokenCache);

var scopes = new string[] 
{
    "offline_access",
    "email",
    "https://outlook.office.com/IMAP.AccessAsUser.All",
    "https://outlook.office.com/POP.AccessAsUser.All",
    "https://outlook.office.com/SMTP.Send",
};

Now acquire an access token and a user name:

string userName;
string accessToken;

var account = (await app.GetAccountsAsync()).FirstOrDefault();
try
{
    AuthenticationResult refresh = await app
        .AcquireTokenSilent(scopes, account)
        .ExecuteAsync();

    userName = refresh.Account.Username;
    accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
    var acquire = await app.AcquireTokenWithDeviceCode(
        scopes, 
        callback=>
    {
        // Write url and code to logs so the operator can react:
        Console.WriteLine(callback.VerificationUrl);
        Console.WriteLine(callback.UserCode);

        // This happens on the first run, manually,
        //  on the operator machine.
        // The code below code is only to illustrate 
        // the operator opening browser on his machine,
        // opening the url and using the code 
        // (extracted from the application logs)
        // to authenticate the app.
        System.Diagnostics.Process.Start(
            new ProcessStartInfo(callback.VerificationUrl) 
                        { UseShellExecute = true }
            );

        return Task.CompletedTask;
    }).ExecuteAsync();

    userName = acquire.Account.Username;
    accessToken = acquire.AccessToken;
}

AcquireTokenWithDeviceCode call waits until operator/administrator gives consent by going to VerificationUrl, entering UserCode and authenticating – this usually happens on a different machine than the application is installed.

Finally your app will exit AcquireTokenWithDeviceCode method and connect using IMAP/POP3/SMTP, authenticate and download emails:

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    // ...

    client.Close();
} 

You can find more details on this flow here:

https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code

Token serialization

Below is a simple implementation that saves MSAL token cache to file. Please note that most likely you should store this cache in an encrypted form:

static class TokenCacheHelper
{
    public static void EnableSerialization(ITokenCache tokenCache)
    {
        tokenCache.SetBeforeAccess(BeforeAccessNotification);
        tokenCache.SetAfterAccess(AfterAccessNotification);
    }

    private static readonly string _fileName = "msalcache.bin3";

    private static readonly object _fileLock = new object();


    private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
    {
        lock (_fileLock)
        {
            byte[] data = null;
            if (File.Exists(_fileName))
                data = File.ReadAllBytes(_fileName);
            args.TokenCache.DeserializeMsalV3(data);
        }
    }

    private static void AfterAccessNotification(TokenCacheNotificationArgs args)
    {
        if (args.HasStateChanged)
        {
            lock (_fileLock)
            {
                byte[] data = args.TokenCache.SerializeMsalV3();
                File.WriteAllBytes(_fileName, data);
            }
        }
    }
};

More details on MSAL token serialization are available here:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization

Extending Sign-in frequency with policies

You can extend how often operator needs to re-authenticate the application up to 1 year:

Side note: Have in mind that similarly a client credential flow requires a client secret which is valid for 2 years maximum.


Get Mail.dll

The post OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
Office365: Temporary server error. Please try again later. PRX4 https://www.limilabs.com/blog/office365-temporary-server-error-please-try-again-later-prx4 Thu, 28 Jan 2021 17:07:20 +0000 https://www.limilabs.com/blog/?p=5777 When using Mail.dll SMTP .NET client to connect to Office365 using OAUTH 2.0 you may receive a following error during authentication phase: Temporary server error. Please try again later. PRX4 SMTP log looks more or less like this: Connecting to ‘outlook.office365.com:587’, SSL/TLS: False. S: 220 AS8PR04CA0136.outlook.office365.com Microsoft ESMTP MAIL Service ready at Thu, 28 Jan […]

The post Office365: Temporary server error. Please try again later. PRX4 first appeared on Blog | Limilabs.

]]>
When using Mail.dll SMTP .NET client to connect to Office365 using OAUTH 2.0 you may receive a following error during authentication phase:

Temporary server error. Please try again later. PRX4

SMTP log looks more or less like this:

Connecting to ‘outlook.office365.com:587’, SSL/TLS: False.

S: 220 AS8PR04CA0136.outlook.office365.com Microsoft ESMTP MAIL Service ready at Thu, 28 Jan 2021 15:43:35 + 0000
C: EHLO[IPv6:2a02:]
S: 250-AS8PR04CA0136.outlook.office365.com Hello[2a02:]
S: 250-SIZE 157286400
S: 250-PIPELINING
S: 250-DSN
S: 250-ENHANCEDSTATUSCODES
S: 250-STARTTLS
S: 250-8BITMIME
S: 250-BINARYMIME
S: 250-CHUNKING
S: 250 SMTPUTF8
C: STARTTLS
S: 220 2.0.0 SMTP server ready
C: EHLO[IPv6:2a02:]
S: 250-AS8PR04CA0136.outlook.office365.com Hello [2a02:]
S: 250-SIZE 157286400
S: 250-PIPELINING
S: 250-DSN
S: 250-ENHANCEDSTATUSCODES
S: 250-AUTH LOGIN XOAUTH2
S: 250-8BITMIME
S: 250-BINARYMIME
S: 250-CHUNKING
S: 250 SMTPUTF8
C: AUTH XOAUTH2 dXNlcj1B...EEBAQ==
S: 451 4.7.0 Temporary server error. Please try again later. PRX4[AS8PR04CA0136.eurprd04.prod.outlook.com]

It is a bug on the Office365 side when new business account is created with Microsoft. 

Although SMTP is enabled by default, it is not working. The workaround for this issue is to turn Authenticated SMTP off and turn it on again.

After creating the e-mail account, you have to edit the account.

Go to https://admin.microsoft.com/ and click Setup.

On the “Active users” list find the account you are using:

Then go to “Mail” tab then click “Manage email apps

There is an option called “Authenticated SMTP”.  It is ticked by default, however to actually make it work you have to uncheck it and save the changes, then go back in and check it again it and save the changes.

You may need to leave it unchecked for some time and wait for some time when it is rechecked, before it starts working.

After this change Mail.dll client can authenticate successfully using OAuth 2.0:

C: AUTH XOAUTH2 dXNlcj1B...BAQ==
S: 235 2.7.0 Authentication successful
C: QUIT

The post Office365: Temporary server error. Please try again later. PRX4 first appeared on Blog | Limilabs.

]]>
OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP https://www.limilabs.com/blog/oauth2-password-grant-office365-exchange-imap-pop3-smtp Thu, 19 Nov 2020 13:47:47 +0000 https://www.limilabs.com/blog/?p=5768 In this series:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP This article shows how to implement OAuth 2.0 password grant flow to access Office365 via IMAP, POP3 […]

The post OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
In this series:

 

This article shows how to implement OAuth 2.0 password grant flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .NET email client.

Enable email protocols

Make sure IMAP/POP3/SMTP is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Disable MFA for account

Password grant flow requires Multi-Factor Authentication (MFA) to be disabled for this mailbox – make also sure there are no Active Directory policies that match this account and require MFA (you can of course have policies that match all other accounts).

Go to Microsoft365 admin center. Select Setup on the left menu and in the Sign-in and security section select Configure multifactor authentication (MFA):

You can use per-user MFA or AD policies.

Register and configure application

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

Enable additional flows:

Then you need to apply correct API permissions and grant the admin consent for your domain.

In the API permissions / Add a permission wizard, select Microsoft Graph and then Delegated permissions to find the following permission scopes listed:

  • offline_access
  • email
  • IMAP.AccessAsUser.All
  • POP.AccessAsUser.All
  • SMTP.Send

Remember to Grant admin consent:

Obtain OAuth 2.0 token

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

string clientId = "Application (client) ID";
string tenantId = "Directory (tenant) ID";

string userEmail = "Username for mailbox";
string userPassword = "Password for that user";

IPublicClientApplication app = PublicClientApplicationBuilder
    .Create(clientId)
    .WithTenantId(tenantId)
    .Build();

var scopes = new string[] 
{
    "offline_access",
    "email",
    "https://outlook.office.com/IMAP.AccessAsUser.All",
    "https://outlook.office.com/POP.AccessAsUser.All",
    "https://outlook.office.com/SMTP.Send",
};

Now acquire an access token and a user name:

string userName;
string accessToken;

var account = (await app.GetAccountsAsync()).FirstOrDefault();

try
{
    AuthenticationResult refresh = await app
        .AcquireTokenSilent(scopes, account)
        .ExecuteAsync();

    userName = refresh.Account.Username;
    accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
    SecureString securePassword = new SecureString();
    foreach (char c in userPassword)
    {
        securePassword.AppendChar(c);
    }

    var result = await app.AcquireTokenByUsernamePassword(
        scopes, 
        userEmail, 
        securePassword).ExecuteAsync();

    userName = result.Account.Username;
    accessToken = result.AccessToken;
}

Install Mail.dll email library

The easiest way to install Mail.dll is to download it from nuget via Package Manager:

PM> Install-Package Mail.dll

Alternatively you can download Mail.dll directly from our website.

Download and process emails

Finally you can connect using IMAP/POP3/SMTP, authenticate and download user’s emails:

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

    client.Close();
} 


Get Mail.dll

The post OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP https://www.limilabs.com/blog/oauth2-office365-exchange-imap-pop3-smtp Tue, 23 Jun 2020 16:24:21 +0000 https://www.limilabs.com/blog/?p=5649 In this series:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow with Office365/Exchange IMAP/POP3/SMTP This article shows how to implement OAuth 2.0 desktop flow to access Office365 via IMAP, POP3 or […]

The post OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
In this series:

 

This article shows how to implement OAuth 2.0 desktop flow to access Office365 via IMAP, POP3 or SMTP using Mail.dll .net email client.

Make sure IMAP/POP3/SMTP is enabled for your organization and mailbox:
Enable IMAP/POP3/SMTP in Office 365

Register your application in Azure Portal, here’s a detailed guide how to do that:
https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app

Remember to add authentication entries (localhost is needed for .net core):

RedirectUri

.NET desktop: https://login.microsoftonline.com/common/oauth2/nativeclient
.NET core/.NET 5,6,7+: http://localhost
ASP.NET: your application custom url

Then you need to apply correct API permissions and grant the admin consent for your domain.

In the API permissions / Add a permission wizard, select Microsoft Graph and then Delegated permissions to find the following permission scopes listed:

  • offline_access
  • email
  • IMAP.AccessAsUser.All
  • POP.AccessAsUser.All
  • SMTP.Send

Remember to Grant admin consent:

Use Microsoft Authentication Library for .NET (MSAL.NET) nuget package to obtain an access token:
https://www.nuget.org/packages/Microsoft.Identity.Client/

string clientId = "Application (client) ID";
string tenantId = "Directory (tenant) ID";

// for @outlook.com/@hotmail accounts instead of setting .WithTenantId use:
// .WithAuthority(AadAuthorityAudience.PersonalMicrosoftAccount)

var app = PublicClientApplicationBuilder
                .Create(clientId)
                .WithTenantId(tenantId)
                .WithDefaultRedirectUri()
                .Build();
// This allows saving access/refresh tokens to some storage
TokenCacheHelper.EnableSerialization(app.UserTokenCache);

var scopes = new string[] 
{
    "offline_access",
    "email",
    "https://outlook.office.com/IMAP.AccessAsUser.All",
    "https://outlook.office.com/POP.AccessAsUser.All",
    "https://outlook.office.com/SMTP.Send",
};

In addition, you should request offline_access scope. When a user approves the offline_access scope, your app can receive refresh tokens from the Microsoft identity platform token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.

Now acquire the access token and user email address:

string userName;
string accessToken;

var account = (await app.GetAccountsAsync()).FirstOrDefault();

try
{
    AuthenticationResult refresh = await app
        .AcquireTokenSilent(scopes, account)
        .ExecuteAsync();

    userName = refresh.Account.Username;
    accessToken = refresh.AccessToken;
}
catch (MsalUiRequiredException e)
{
    var result = await app.AcquireTokenInteractive(scopes)
        .ExecuteAsync();

    userName = result.Account.Username;
    accessToken = result.AccessToken;
}

On the first run user will see a Microsoft login screen, with option to log-in, using a known account and granting access to the app (if needed):

Finally you can connect using IMAP/POP3/SMTP, authenticate and download user’s emails:

using (Imap client = new Imap())
{
    client.ConnectSSL("outlook.office365.com");
    client.LoginOAUTH2(userName, accessToken);
 
    client.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
                .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
   }

    client.Close();
} 

Any organization and personal accounts

To access accounts from any organization and personal accounts as well, you need to specify correct account types when you create the App in your AD:

Additionally you need to use:    

    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )

instead of

    .WithTenantId(tenantId)

when creating the app:

 var app = PublicClientApplicationBuilder
    .Create(clientId)
    .WithAuthority(
        AadAuthorityAudience.AzureAdAndPersonalMicrosoftAccount
        )
    .WithDefaultRedirectUri()
    .Build();

Token serialization

Below is a simple implementation that saves MSAL token cache to file:

static class TokenCacheHelper
{
    public static void EnableSerialization(ITokenCache tokenCache)
    {
        tokenCache.SetBeforeAccess(BeforeAccessNotification);
        tokenCache.SetAfterAccess(AfterAccessNotification);
    }

    private static readonly string _fileName = "msalcache.bin3";

    private static readonly object _fileLock = new object();


    private static void BeforeAccessNotification(TokenCacheNotificationArgs args)
    {
        lock (_fileLock)
        {
            byte[] data = null;
            if (File.Exists(_fileName))
                data = File.ReadAllBytes(_fileName);
            args.TokenCache.DeserializeMsalV3(data);
        }
    }

    private static void AfterAccessNotification(TokenCacheNotificationArgs args)
    {
        if (args.HasStateChanged)
        {
            lock (_fileLock)
            {
                byte[] data = args.TokenCache.SerializeMsalV3();
                File.WriteAllBytes(_fileName, data);
            }
        }
    }
};

Please note that most likely you should store this cache in an encrypted form in some kind of a database.
Consider using MSAL token serialization implementations available here:

https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization


Get Mail.dll

The post OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for installed applications https://www.limilabs.com/blog/oauth2-gmail-imap-installed-applications Mon, 13 Mar 2017 05:01:09 +0000 http://www.limilabs.com/blog/?p=3359 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method […]

The post OAuth 2.0 with Gmail over IMAP for installed applications first appeared on Blog | Limilabs.

]]>
You can also read how to use:

 

OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

This article describes using OAuth 2.0 to access Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can also use OAuth 2.0 for web applications.

Google.Apis

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Cloud Console. After you’ve registered copy the “Client ID” and “Client secret” values which you’ll need later.

At least product name must be specified:

Now create credentials:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret and scope variables, as well as Google OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

// C#

string clientID = "XXX.apps.googleusercontent.com";
string clientSecret = "IxBs0g5sdaSDUz4Ea7Ix-Ua";

var clientSecrets = new ClientSecrets
{
    ClientId = clientID,
    ClientSecret = clientSecret
};

var credential = new GoogleAuthorizationCodeFlow(
    new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = clientSecrets,
        Scopes = new[] { 
            GoogleScope.ImapAndSmtp.Name, 
            GoogleScope.UserInfoEmailScope.Name}
    });
' VB.NET 

Dim clientID As String = "XXX.apps.googleusercontent.com"
Dim clientSecret As String = "IxBs0g5sdaSDUz4Ea7Ix-Ua"

Dim clientSecrets = New ClientSecrets With { _
	.ClientId = clientID, _
	.ClientSecret = clientSecret _
}

Dim credential = New GoogleAuthorizationCodeFlow( _
    New GoogleAuthorizationCodeFlow.Initializer With { _
    	.ClientSecrets = clientSecrets, _
	    .Scopes = { _
            GoogleScope.ImapAndSmtp.Name,  _
            GoogleScope.UserInfoEmailScope.Name} _
})

Obtain an OAuth 2.0 access token

Now we’ll create authorization url.

As OOB addresses are no longer supported, to receive the authorization code using this URL, your application must be listening on the local web server. This is the recommended mechanism for obtaining the authorization code.

When your app receives the authorization response, for best usability it should respond by displaying an HTML page that instructs the user to close the browser and return to your app.

Your application needs to create a server that listens on this local address:

http://127.0.0.1:port or http://[::1]:port or http://localhost:port

Query your platform for the relevant loopback IP address and start an HTTP listener on a random available port. Substitute port with the actual port number your app listens on.

// C#

AuthorizationCodeRequestUrl url = credential
    .CreateAuthorizationCodeRequest("http://127.0.0.1:1234/auth2callback");

Process.Start(url.Build().ToString());
' VB.NET 

Dim url As AuthorizationCodeRequestUrl = credential _
    .CreateAuthorizationCodeRequest("http://127.0.0.1:1234/auth2callback")

Process.Start(url.Build().ToString())

We are using Process.Start here, but you can also embed WebBrowser control in your application.

At this point user is redirected to Google to authorize the access:

After this step user is redirected to the loopback address you provided.

Your application’s local web server, listening on this address, should obtain code from the url parameter.

Following is a code that reads this code and contacts Google to exchange it for a refresh-token and an access-token:

string authCode = "get from the url";

TokenResponse token = await credential.ExchangeCodeForTokenAsync(
    "", 
    authCode, 
    "http://127.0.0.1/auth2callback", 
    CancellationToken.None);

string accessToken = token.AccessToken;
' VB.NET 

Dim authCode As String = "get from the url";

Dim token As TokenResponse = Await credential.ExchangeCodeForTokenAsync( _
    "",  _
    authCode,  _
    "http://127.0.0.1/auth2callback",  _
    CancellationToken.None)

Dim accessToken As String = token.AccessToken

An access token is usually valid for a maximum of one hour, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

// C#

GoogleApi api = new GoogleApi(accessToken);
string user = api.GetEmail();

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}
' VB.NET 

Dim api As New GoogleApi(accessToken)
Dim user As String = api.GetEmail()

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(user, accessToken)

	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search(Flag.Unseen)

	For Each uid As Long In uids
		Dim eml = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next
	imap.Close()
End Using

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.

Your refresh token will be sent only once – don’t loose it!

We recommend storing entire TokenResponse object received from GoogleAuthorizationCodeFlow.ExchangeCodeForTokenAsync method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

// c#

TokenResponse refreshed = await credential.RefreshTokenAsync(
    "", 
    token.RefreshToken, 
    CancellationToken.None);
' VB.NET 

Dim refreshed As TokenResponse = Await credential.RefreshTokenAsync( _
    "",  _
    token.RefreshToken,  _
    CancellationToken.None)

The post OAuth 2.0 with Gmail over IMAP for installed applications first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for web applications https://www.limilabs.com/blog/oauth2-gmail-imap-web-applications Mon, 13 Mar 2017 05:00:39 +0000 http://www.limilabs.com/blog/?p=3328 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method […]

The post OAuth 2.0 with Gmail over IMAP for web applications first appeared on Blog | Limilabs.

]]>
You can also read how to use:

 

OAuth 2.0 is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

This article describes using OAuth 2.0 to access Gmail IMAP and SMTP servers using .NET IMAP component in web application scenario (ASP.NET/ASP.NET MVC). You can also use OAuth 2.0 for installed applications.

Google.Apis

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Cloud Console. After you’ve registered, specify “Redirect URI” and copy the “Client ID” and “Client secret” values which you’ll need later.

At least product name must be specified:

Now create credentials:

Specify redirect URI:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret, redirect url and scope variables, as well as Google OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

// C#

string clientID = "XXX.apps.googleusercontent.com";
string clientSecret = "IxBs0g5sdaSDUz4Ea7Ix-Ua";
string redirectUri = "https://www.example.com/oauth2callback";

var clientSecrets = new ClientSecrets
{
    ClientId = clientID,
    ClientSecret = clientSecret
};

var credential = new GoogleAuthorizationCodeFlow(
    new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = clientSecrets,
            Scopes = new[] { 
                GoogleScope.ImapAndSmtp.Name, 
                GoogleScope.UserInfoEmailScope.Name}
        });
' VB.NET 

Dim clientID As String = "XXX.apps.googleusercontent.com"
Dim clientSecret As String = "IxBs0g5sdaSDUz4Ea7Ix-Ua"
Dim redirectUri As String = "https://www.example.com/oauth2callback"

Dim clientSecrets = New ClientSecrets With { _
	.ClientId = clientID, _
	.ClientSecret = clientSecret _
}

Dim credential = New GoogleAuthorizationCodeFlow(New GoogleAuthorizationCodeFlow.Initializer With { _
	.ClientSecrets = clientSecrets, _
	.Scopes = {GoogleScope.ImapAndSmtp.Name, GoogleScope.UserInfoEmailScope.Name} _
})

Obtain an OAuth 2.0 access token

Now we’ll create authorization url:

// C#

AuthorizationCodeRequestUrl url = credential
    .CreateAuthorizationCodeRequest(redirectUri);
' VB.NET 

Dim url As AuthorizationCodeRequestUrl = credential _
    .CreateAuthorizationCodeRequest(redirectUri)

Now we need to redirect the client:

// c#

return new RedirectResult(url.Build().ToString());
' VB.NET 

Return New RedirectResult(url.Build().ToString())

At this point user is redirected to Google to authorize the access:

After this step user is redirected back to your website (https://www.example.com/oauth2callback), with code request parameter:
https://www.example.com/oauth2callback?code=4/5Y7M4cARD9hrt0nuKnQa0YgasdbwprRtIIjk4Fus#

// C#

public class OAauth2CallbackController : Controller
{
    public ActionResult Index(string code)
    {
        ...
    }
}
' VB.NET 

Public Class OAauth2CallbackController
    Inherits Controller
    Public Function Index(code As String) As ActionResult
        ...
    End Function
End Class

Following is this callback code. Its purpose is to get a refresh-token and an access-token:

// c#

string authCode = code;

TokenResponse token = await credential.ExchangeCodeForTokenAsync(
    "", 
    authCode, 
    redirectUri, 
    CancellationToken.None);

string accessToken = token.AccessToken;
' VB.NET 

Dim authCode As String = code

Dim token As TokenResponse = Await credential.ExchangeCodeForTokenAsync( _
    "",  _
    authCode,  _
    redirectUri,  _
    CancellationToken.None)

Dim accessToken As String = token.AccessToken

An access token is usually valid for a maximum of one hour, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

// c#

GoogleApi api = new GoogleApi(accessToken);
string user = api.GetEmail();

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}
' VB.NET 

Dim api As New GoogleApi(accessToken)
Dim user As String = api.GetEmail()

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(user, accessToken)

	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search(Flag.Unseen)

	For Each uid As Long In uids
		Dim eml = imap.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next
	imap.Close()
End Using

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.

Your refresh token will be sent only once – don’t loose it!

We recommend storing entire TokenResponse object received from GoogleAuthorizationCodeFlow.ExchangeCodeForTokenAsync method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

// c#

TokenResponse refreshed = await credential.RefreshTokenAsync(
    "", 
    token.RefreshToken, 
    CancellationToken.None);
' VB.NET 

Dim refreshed As TokenResponse = Await credential.RefreshTokenAsync( _
    "",  _
    token.RefreshToken,  _
    CancellationToken.None)

The post OAuth 2.0 with Gmail over IMAP for web applications first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) https://www.limilabs.com/blog/oauth2-gmail-imap-installed-applications-dotnetopenauth Mon, 13 Mar 2017 04:00:42 +0000 https://www.limilabs.com/blog/?p=5278 Consider using Google.Apis version for installed applications instead of DotNetOpenAuth version. OAuth is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications. This article describes using OAuth 2.0 to access Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can […]

The post OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Consider using Google.Apis version for installed applications instead of DotNetOpenAuth version.

OAuth is an open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.

This article describes using OAuth 2.0 to access Gmail IMAP and SMTP servers using .NET IMAP component in installed applications scenario. You can also use OAuth 2.0 for web applications.

DotNetOpenAuth

First download the latest version of DotNetOpenAuth – it’s free, open source library that implements OAuth 2.0: http://www.dotnetopenauth.net

Register Application

Before you can use OAuth 2.0, you must register your application using the Google Developers Console.

At least product name must be specified:

Now create credentials:

After you’ve registered, copy the “Client ID” and “Client secret” values, which you’ll need later:

Now we can define clientID, clientSecret and scope variables, as well as Google OAuth 2.0 server addresses. Scope basically specifies what services we want to have access to. In our case it is user’s email address and IMAP/SMTP access:

const string clientID = "12345.apps.googleusercontent.com";
const string clientSecret = "XXXYYY111";

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };
List<string> scope = new List<string>
    {
        GoogleScope.ImapAndSmtp.Name,
        GoogleScope.UserInfoEmailScope.Name
    };

Obtain an OAuth 2.0 access token

As we are using installed applications scenario we’ll use NativeApplicationClient class.

Because of a small issue in DotNetOpenAuth we can not use UserAgentClient directly. NativeApplicationClient inherits UserAgentClient and workarounds this issue. You can find the implementation of NativeApplicationClient on the bottom of this article.

NativeApplicationClient consumer = new NativeApplicationClient(server, clientID, clientSecret);
Uri userAuthorizationUri = consumer.RequestUserAuthorization(scope);

Process.Start(userAuthorizationUri.AbsoluteUri);

We are using Process.Start here, but you can also embed WebBrowser control in your application.

At this point user is redirected to Google to authorize the access:

As OOB addresses are no longer supported, to receive the authorization code using this URL, your application must be listening on the local web server. This is the recommended mechanism for obtaining the authorization code.

When your app receives the authorization response, for best usability it should respond by displaying an HTML page that instructs the user to close the browser and return to your app.

Your application needs to create a server that listens on this local address:

http://127.0.0.1:port or http://[::1]:port or http://localhost:port

Query your platform for the relevant loopback IP address and start an HTTP listener on a random available port. Substitute port with the actual port number your app listens on.

In our example we’ll use http://127.0.0.1/auth2callback address.

After this step user is presented a code that needs to be pasted to your application:

Please note that this code also appears in the title of the browser:

  • It is possible to monitor processes on your machine and act automatically when it is there.
  • If you use embedded WebBrowser control in your application, you can monitor the HTML document title after any redirect.

Following is a code that reads this code and contacts Google to exchange it for a refresh-token and an access-token:

string authCode = Console.ReadLine();

consumer.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(clientSecret);

IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(authCode);

string accessToken = grantedAccess.AccessToken;

An access token is usually valid for a maximum of one hour, and allows you to access the user’s data. You also received a refresh token. A refresh token can be used to request a new access token once the previous expired.

Access IMAP/SMTP server

Finally we’ll ask Google for user’s email and use LoginOAUTH2 method to access Gmail’s IMAP server:

GoogleApi api = new GoogleApi(accessToken);
string user = api.GetEmail();

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(user, accessToken);

    imap.SelectInbox();
    List<long>; uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = imap.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    imap.Close();
}

NativeApplicationClient class

The OAuth 2.0 client for use by native applications. It’s main purpose is to build a generic URL containing the auth code, because DotNetOpenAuth library only allows parsing an URL as a method of retrieving the AuthorizationState.

/// <summary>
/// The OAuth2 client for use by native applications.
/// This is a partial implementation which  should be used until
/// the feature has been fully implemented in DotNetOpenAuth.
/// </summary>
public class NativeApplicationClient : UserAgentClient
{
    /// <summary>
    /// Represents a callback URL which points to a special out of band page
    /// used for native OAuth2 authorization. This URL will cause the authorization
    /// code to appear in the title of the window.
    /// </summary>
    /// <remarks>
    /// See http://code.google.com/apis/accounts/docs/OAuth2.html
    /// </remarks>
    public const string OutOfBandCallbackUrl = "http://127.0.0.1/auth2callback";

    /// <summary>
    /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
    /// </summary>
    /// <param name="authorizationServer">The token issuer.</param>
    /// <param name="clientIdentifier">The client identifier.</param>
    /// <param name="clientSecret">The client secret.</param>
    public NativeApplicationClient(
        AuthorizationServerDescription authorizationServer,
        string clientIdentifier,
        string clientSecret)
        : base(authorizationServer, clientIdentifier, clientSecret)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="UserAgentClient"/> class.
    /// </summary>
    /// <param name="authorizationServer">The token issuer.</param>
    public NativeApplicationClient(
        AuthorizationServerDescription authorizationServer)
        : this(authorizationServer, null, null)
    {
    }

    /// <summary>
    /// Creates the URL which should be used by the user to request the initial
    /// authorization. Uses the default Out-of-band-URI as a callback.
    /// </summary>
    /// <param name="scope">Set of requested scopes</param>
    /// <returns>URI pointing to the authorization server</returns>
    public Uri RequestUserAuthorization(IEnumerable<string> scope)
    {
        var state = new AuthorizationState(scope);
        state.Callback = new Uri(OutOfBandCallbackUrl);
        return RequestUserAuthorization(state, false, null);
    }

    /// <summary>
    /// Uses the provided authorization code to create an authorization state.
    /// </summary>
    /// <param name="authCode">The authorization code for getting an access token.</param>
    /// <param name="authorizationState">The authorization.  Optional.</param>
    /// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
    public IAuthorizationState ProcessUserAuthorization(
        string authCode,
        IAuthorizationState authorizationState)
    {
        if (authorizationState == null)
        {
            authorizationState = new AuthorizationState(null);
            authorizationState.Callback = new Uri(OutOfBandCallbackUrl);
        }

        // Build a generic URL containing the auth code.
        // This is done here as we cannot modify the DotNetOpenAuth library
        // and the underlying method only allows parsing an URL as a method
        // of retrieving the AuthorizationState.
        string url = "http://example.com/?code=" + authCode;
        return ProcessUserAuthorization(new Uri(url), authorizationState);
    }

    /// <summary>
    /// Uses the provided authorization code to create an authorization state.
    /// </summary>
    /// <param name="authCode">The authorization code for getting an access token.</param>
    /// <returns>The granted authorization, or <c>null</c> if the authorization was null or rejected.</returns>
    public IAuthorizationState ProcessUserAuthorization(string authCode)
    {
        return ProcessUserAuthorization(authCode, null);
    }
};

Refreshing access token

An access token is usually short lived and valid for a maximum of one hour. The main reason behind this is security and prevention of replay attacks. This means that for long-lived applications you need to refresh the access token.

Your refresh token will be sent only once – don’t loose it!

We recommend storing entire IAuthorizationState object received from NativeApplicationClient.ProcessUserAuthorization method call. This object contains both: refresh token and access token, along with its expiration time.

The process of refreshing access token is simple:

IAuthorizationState grantedAccess = ...
consumer.RefreshAuthorization(grantedAccess, TimeSpan.FromMinutes(20));

In the example above the access token will not be refreshed if its remaining lifetime exceeds 20 minutes.

Retrieving lost refresh token

When your application receives a refresh token, it is important to store that refresh token for future use. If your application loses the refresh token, it will have to re-prompt the user for consent before obtaining another refresh token.

You’ll need to add approval_prompt=force to your parameters:

AuthorizationServerDescription authServer = new AuthorizationServerDescription
{
    AuthorizationEndpoint =
        new Uri("https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force"),
    ...
};

The post OAuth 2.0 with Gmail over IMAP for installed applications (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) https://www.limilabs.com/blog/oauth2-gmail-imap-service-account-dotnetopenauth Sat, 12 Mar 2016 22:00:00 +0000 http://www.limilabs.com/blog/?p=4810 Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version. In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password. […]

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version.

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

This scenario is very similar to 2-legged OAuth, which uses OAuth 1.0a. Although it still works, it has been deprecated by Google and OAuth 2.0 service accounts were introduced.

The following describes how to use XOAUTH2 and OAuth 2.0 to achieve the equivalent of 2-legged OAuth.

Google APIs console

First you need to visit the Google Developers Console and create a service account:

console_0
console_1
console_2

Download and save this private key, you’ll need that later:

console_3

Go to “Service accounts” and click “View Client ID”:

console_4

Make a note of the Client ID and Email address (Service account).

Google Apps Dashboard

Next step is to authorize access for newly created service account.

Visit your domain administration panel:
https://www.google.com/a/cpanel/yourdomain.com/ManageOauthClients

Then click “Advanced tools”, “Authentication” and “Manage third party OAuth Client access”.

On this screen you can authorize service account to access email scope:

cpanel_1

Use previously remembered Client ID and “https://mail.google.com/”, which is IMAP/SMTP API scope:

Client Name: 1234567890
One or More API Scopes: https://mail.google.com/

DotNetOpenAuth

DotNetOpenAuth is free, open source library that implements OAuth 2.0.

However you can not use the latest version. This is because Google has not updated their code to work with the most recent release. You’ll need to use 4.0 version.

Newtonsoft.Json

The other library you’ll need is Newtonsoft.Json JSON library. You can download it here: https://github.com/JamesNK/Newtonsoft.Json/releases or use nuget.

Access IMAP/SMTP server

using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Newtonsoft.Json;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.OAuth2.Messages;
using Limilabs.Client.Authentication.Google;
using Limilabs.Client.IMAP;
using Limilabs.Mail;

const string serviceAccountEmail = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.com";

X509Certificate2 certificate =  new X509Certificate2(
    serviceAccountCertPath,
    serviceAccountCertPassword,
    X509KeyStorageFlags.Exportable);

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };

AssertionFlowClient provider = new AssertionFlowClient(server, certificate)
{
    ServiceAccountId = serviceAccountEmail,
    Scope = Limilabs.Client.Authentication.Google.GoogleScope.ImapAndSmtp.Name,
    ServiceAccountUser = userEmail,
};

IAuthorizationState grantedAccess = AssertionFlowClient.GetState(provider);
string accessToken = grantedAccess.AccessToken;

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.LoginOAUTH2(userEmail, accessToken);

    client.SelectInbox();
    List<long> uids = client.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = client.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    client.Close();
}




AssertionFlowClient

I’ll need to add several classes defined below. You can also browse Google APIs Client Library for .NET.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Limilabs.Client;
using Limilabs.Client.Authentication.SSPI.Ntlm;

/// <summary>
/// Assertion flow header used to generate the assertion flow message header.
/// </summary>
public class AssertionFlowHeader
{
    /// <summary>
    /// Gets or sets the encryption algorithm used by the assertion flow message.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("alg")]
    public String Algorithm { get; set; }

    /// <summary>
    /// Gets or sets the type of the claim.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("typ")]
    public String Type { get; set; }
};

/// <summary>
/// Google assertion flow header holding Google supported values.
/// </summary>
public class GoogleAssertionFlowHeader : AssertionFlowHeader
{

    /// <summary>
    /// The google signing algorithm, currently RSA-SHA256
    /// </summary>
    public const string GoogleSigningAlgorithm = "RS256";

    /// <summary>
    /// The type of the google assertion, currently JSON Web Token
    /// </summary>
    public const string GoogleAssertionType = "JWT";

    public GoogleAssertionFlowHeader()
    {
        Algorithm = GoogleSigningAlgorithm;
        Type = GoogleAssertionType;
    }
};

/// <summary>
/// Assertion flow claim used to generate the assertion flow message claim.
/// </summary>
public class AssertionFlowClaim
{
    public AssertionFlowClaim()
    {
        DateTime begin = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc);
        IssuedAt = (long)(DateTime.UtcNow - begin).TotalSeconds;
        ExpiresAt = IssuedAt + 3600;
    }

    public AssertionFlowClaim(AuthorizationServerDescription authorizationServer)
        : this()
    {
        Audience = authorizationServer.TokenEndpoint.ToString();
    }

    /// <summary>
    /// Gets or sets the assertion flow issuer (e.g client ID).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iss")]
    public String Issuer { get; set; }

    /// <summary>
    /// Gets or sets the service account user (for domain-wide delegation).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("prn")]
    public String Principal { get; set; }

    /// <summary>
    /// Gets or sets the scope.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("scope")]
    public String Scope { get; set; }

    /// <summary>
    /// Gets or sets the token endpoint.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("aud")]
    public String Audience { get; set; }

    /// <summary>
    /// Gets or sets the expected expiration of the token to retrieve.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("exp")]
    public long ExpiresAt { get; set; }

    /// <summary>
    /// Gets or sets the UTC timestamp at which this claim has been built.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iat")]
    public long IssuedAt { get; set; }
};

/// <summary>
/// Assertion flow message to be sent to the token endpoint.
/// </summary>
public class AssertionFlowMessage : MessageBase
{
    /// <summary>
    /// Google supported assertion type
    /// </summary>
    public const string GoogleAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer";

    /// <summary>
    /// Initializes a new instance of the <see cref="AssertionFlowMessage"/> class.
    /// </summary>
    /// <param name='authorizationServer'> Authorization server description. </param>
    public AssertionFlowMessage(AuthorizationServerDescription authorizationServer) :
        base(new Version(2, 0), MessageTransport.Direct, authorizationServer.TokenEndpoint)
    {
        GrantType = "assertion";
        AssertionType = GoogleAssertionType;
        this.HttpMethods = HttpDeliveryMethods.PostRequest;
    }

    /// <summary>
    /// Gets or sets the type of the grant (defaults to "assertion").
    /// </summary>
    [MessagePart("grant_type", IsRequired = true)]
    public String GrantType { get; set; }

    /// <summary>
    /// Gets or sets the type of the assertion
    /// (defaults to "http://oauth.net/grant_type/jwt/1.0/bearer").
    /// </summary>
    [MessagePart("assertion_type", IsRequired = true)]
    public String AssertionType { get; set; }

    /// <summary>
    /// Gets or sets the assertion message.
    /// </summary>
    [MessagePart("assertion", IsRequired = true)]
    public String Assertion { get; set; }
};

public class AssertionFlowClient : ClientBase
{
    /// <summary>
    /// Gets or sets the service account identifier.
    /// </summary>
    /// <value>
    /// The service account identifier.
    /// </value>
    public String ServiceAccountId { get; set; }

    /// <summary>
    /// Gets or sets the service account user (used for domain-wide delegation).
    /// </summary>
    public String ServiceAccountUser { get; set; }

    /// <summary>
    /// Gets or sets the scope to get access for.
    /// </summary>
    public String Scope { get; set; }

    /// <summary>
    /// Gets the certificate used to sign the assertion.
    /// </summary>
    public X509Certificate2 Certificate { get; private set; }

    /// <summary>
    /// Gets or sets the JWT claim's header (defaults to Google's supported values).
    /// </summary>
    public AssertionFlowHeader Header { get; set; }

    public RSACryptoServiceProvider Key { get; private set; }

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="AssertionFlowClient"/> class.
    /// </summary>
    /// <param name='authorizationServer'>
    /// Authorization server description.
    /// </param>
    /// <param name='certificate'>
    /// Certificate to use to sign the assertion flow messages.
    /// </param>
    public AssertionFlowClient(
        AuthorizationServerDescription authorizationServer,
        X509Certificate2 certificate)
        : base(authorizationServer, null, null)
    {
        if (certificate == null)
            throw new ArgumentNullException("certificate");
        if (certificate.PrivateKey == null)
            throw new ArgumentNullException("certificate.PrivateKey");

        Header = new GoogleAssertionFlowHeader();
        Certificate = certificate;

        // Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
        byte[] privateKeyBlob = rsa.ExportCspBlob(true);

        Key = new RSACryptoServiceProvider();
        Key.ImportCspBlob(privateKeyBlob);
    }

    /// <summary>
    /// Helper method to retrieve the Authorization State.
    /// </summary>
    /// <returns>
    /// The authorization state.
    /// </returns>
    /// <param name='provider'>
    /// The provider to use to retrieve the authorization state.
    /// </param>
    public static IAuthorizationState GetState(AssertionFlowClient provider)
    {
        if (provider.Scope == null)
            throw new ArgumentNullException("Scope");
        IAuthorizationState state = new AuthorizationState(provider.Scope.Split(' '));

        if (provider.RefreshToken(state, null))
        {
            return state;
        }
        return null;
    }

    /// <summary>
    /// Request a new access token using the OAuth 2.0 assertion flow.
    /// </summary>
    /// <returns>
    /// Whether or not a new access token has been successfully retrieved.
    /// </returns>
    /// <param name='authorization'>
    /// Object containing the current authorization state.
    /// </param>
    /// <param name='skipIfUsefulLifeExceeds'>
    /// If set to <c>true</c> skip if useful life exceeds.
    /// </param>
    public new bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds)
    {
        return RefreshToken(authorization, skipIfUsefulLifeExceeds, this.Channel.Request);
    }

    public bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds,
        Func<IDirectedProtocolMessage, IProtocolMessage> requestProvider)
    {
        if (authorization == null)
            throw new ArgumentNullException("authorization");
        if (this.Certificate == null)
            throw new ArgumentNullException("Certificate");

        // Check if the token is still valid.
        if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue)
        {
            TimeSpan timeSpan = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
            if (timeSpan > skipIfUsefulLifeExceeds.Value)
            {
                return false;
            }
        }

        AssertionFlowMessage requestMessage = GenerateMessage();

        var response = requestProvider(requestMessage);

        // Response is not strongly-typed to an AccessTokenSuccessResponse because DotNetOpenAuth can't infer the
        // type from the request message type. The only way to get access to the result data is through the
        // resulting Dictionary.
        if (response.ExtraData.ContainsKey("access_token") && response.ExtraData.ContainsKey("expires_in"))
        {
            authorization.AccessToken = response.ExtraData["access_token"];
            long expiresIn = long.Parse(response.ExtraData["expires_in"]);
            DateTime utcNow = DateTime.UtcNow;
            authorization.AccessTokenExpirationUtc = utcNow.AddSeconds(expiresIn);
            authorization.AccessTokenIssueDateUtc = utcNow;
            authorization.SaveChanges();
            return true;
        }
        return false;
    }

    /// <summary>
    /// Generates the assertion flow message to be sent to the token endpoint.
    /// </summary>
    /// <returns>
    /// The assertion flow message.
    /// </returns>
    private AssertionFlowMessage GenerateMessage()
    {
        string header = JsonConvert.SerializeObject(Header);
        string claim = JsonConvert.SerializeObject(
            new AssertionFlowClaim(AuthorizationServer)
            {
                Issuer = this.ServiceAccountId,
                Principal = this.ServiceAccountUser,
                Scope = this.Scope
            });

        StringBuilder assertion = new StringBuilder();
        assertion.Append(UnpaddedUrlSafeBase64Encode(header));
        assertion.Append(".");
        assertion.Append(UnpaddedUrlSafeBase64Encode(claim));

        // TODO: Check if this is working on FIPS enabled systems.
        byte[] data = Encoding.ASCII.GetBytes(assertion.ToString());
        String signature = UnpaddedUrlSafeBase64Encode(Key.SignData(data, "SHA256"));
        assertion.Append(".");
        assertion.Append(signature);

        return new AssertionFlowMessage(this.AuthorizationServer)
        {
            Assertion = assertion.ToString()
        };
    }

    /// <summary>
    /// Encode the provided UTF8 string into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='value'>
    /// String to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(String value)
    {
        return UnpaddedUrlSafeBase64Encode(Encoding.UTF8.GetBytes(value));
    }

    /// <summary>
    /// Encode the byte array into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='bytes'>
    /// Bytes to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(Byte[] bytes)
    {
        return Convert.ToBase64String(bytes)
            .Replace("=", String.Empty)
            .Replace('+', '-')
            .Replace('/', '_');
    }
};

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>