OAUTH2(OPENID Connect) + MVC 5. refresh token issue - oauth-2.0

I'm looking for any useful suggestion with regards to obtaining refresh_token using OWIN libs.
I follow article http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on
all stuff works sweet. but no way, at least I don't know the way, to retrieve refresh_token
I've tried to add "access_offline" to initial request, nothing useful. I definitely do something wrong.
Why do I need it? - I'm trying to chain functionality of Google Glass + MVC5 + OWIN.
p.s. I will appreciate any link to working sample built using Google.Api.Auth.
Thanks.

Take a look in the following blogpost:
http://peleyal.blogspot.com/2014/01/aspnet-mvc-with-google-openid-and-oauth.html
And, you can find a working sample code in https://github.com/peleyal/peleyal/tree/master/Google.Apis.Sample.MVC.

Yea, there's an open issue related to this in the Katana project:
https://katanaproject.codeplex.com/workitem/227
In short, it needs to be easier.

You can use a code like this
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "YOUR_CLIENT_ID",
ClientSecret = "YOUR_CLIENT_SECRET",
AccessType = "offline",
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = (context) =>
{
TimeSpan expiryDuration = context.ExpiresIn ?? new TimeSpan();
context.Identity.AddClaim(new Claim("urn:tokens:google:email", context.Email));
context.Identity.AddClaim(new Claim("urn:tokens:google:url", context.GivenName));
if (!String.IsNullOrEmpty(context.RefreshToken))
{
context.Identity.AddClaim(new Claim("urn:tokens:google:refreshtoken", context.RefreshToken));
}
context.Identity.AddClaim(new Claim("urn:tokens:google:accesstoken", context.AccessToken));
context.Identity.AddClaim(new Claim("urn:tokens:google:accesstokenexpiry", DateTime.Now.Add(expiryDuration).ToString()));
return Task.FromResult(0);
}
}
});

Related

ASP.NET MVC with Azure B2C refresh ID token issue

I am developing an ASP.NET MVC app with Azure B2C authentication. It is required that, after the ID token expires (IIS session not expires), any subsequent action call should automatically refresh the ID token with the refresh token and then continue the execution without re-login.
Questions:
Does the solution make sense?
After refreshing the ID token and set the cookies, how can I redirect to the original url and continue execution without re-login?
Thanks, any idea is highly appreciated.
This is my code:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var refreshToken = HttpContext.Current.Request.Cookies["msal.refreshtoken"];
if (refreshToken != null && !string.IsNullOrEmpty(refreshToken.Value))
{
var newIdToken = TokenService.RefreshIdToken(refreshToken.Value);
var idTokenCookie = new HttpCookie("msal.idtoken", newIdToken)
{
Secure = true,
HttpOnly = true
};
HttpContext.Current.Response.Cookies.Set(idTokenCookie);
return;
}
}
// TokenService.RefreshIdToken
public static string RefreshIdToken(string refreshToken)
{
var policyName = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
var B2CDomain = ConfigurationManager.AppSettings["ida:B2CDomain"];
var tenant = ConfigurationManager.AppSettings["ida:Tenant"];
var clientId = ConfigurationManager.AppSettings["ida:ClientId"];
var clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
var tokenEndpointUri = $"https://{B2CDomain}/{tenant}/{policyName}/oauth2/v2.0/token";
var httpClient = new HttpClient();
var requestBodyDict = new Dictionary<string, string>
{
{ "grant_type" , "refresh_token" },
{ "client_id" , clientId },
{ "client_secret" , clientSecret },
{ "scope" , $"openid" },
{ "refresh_token" , refreshToken }
};
var request = new HttpRequestMessage
{
RequestUri = new Uri(tokenEndpointUri),
Method = HttpMethod.Post,
Content = new FormUrlEncodedContent(requestBodyDict)
};
var task = Task.Run(() => httpClient.SendAsync(request));
task.Wait();
var response = task.Result;
var task1 = Task.Run(() => response.Content.ReadAsStringAsync());
task1.Wait();
var responseString = task1.Result;
if (response.IsSuccessStatusCode)
{
var idToken = (string)JsonConvert.DeserializeObject<dynamic>(responseString).id_token.ToString();
return idToken;
}
else
{
throw new Exception();
}
}
A couple of thoughts that are too long to put in comments:
Yes the basic idea of ‘use the refresh token to get a new id token’ is how it’s supposed to work.
Googling this question suggests a bewildering array of examples to imitate :-( e.g. Microsoft’s Azure Samples on GitHub for A/D auth for a web app (as opposed to webapi or SPA)
The basic plan for identity problems like this is, find an authoritative example and follow it because that reduces your risk of embarrassing error. ( For instance, Auth0’s example for this scenario says to get a new refresh_token as well as a new id_token. Not doing that might be okay but then the user will be forced to re-login when the refresh token expires. Then you’ll be tempted to use ultra-long-lifetime refresh token, loosening your security a little)
If you can’t find an authoritative example, considering raising an issue or commenting on one.
OTOH, if the code you’ve written works, then maybe you’ve done!
The problem with finding an example to imitate after you’ve got started is trying to find just the right the example for the technology choices you already made. It may be easier to start with an empty project, follow a tutorial, get the tutorial working, then copy the solution back into your app.
To send your user back to their original target you should be able to
var originalUrl= HttpContext.Current.Request.Url;
HttpContext.Current.Response.Redirect(original);
But only do that if getting the id_token succeeded otherwise it creates an infinite loop.

Mailkit Can't authenticate with O365 oAuth2 account

I tried to authenticate on a O365 application I created on the Azure portal and it doesn't work as expected.
The following code works well but it's using a login/password and it's not recommended by Microsoft. (found here https://github.com/jstedfast/MailKit/issues/989)
var scopes = new[] { "https://outlook.office365.com/IMAP.AccessAsUser.All" };
var confidentialClientApplication = PublicClientApplicationBuilder.Create(_clientId).WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs).Build();
SecureString securePassword = new NetworkCredential("", _userPassword).SecurePassword;
var acquireTokenByUsernamePasswordParameterBuilder = confidentialClientApplication.AcquireTokenByUsernamePassword(scopes, _userMail, securePassword);
var authenticationResult = acquireTokenByUsernamePasswordParameterBuilder.ExecuteAsync().Result;
if (_debugCall)
{
imapClient = new ImapClient(new ProtocolLogger(_configurationId + "_IMAP_" + DateTime.Now.ToString("yyyyMMddHHssffff") + ".log"));
}
else
{
imapClient = new ImapClient();
}
imapClient.CheckCertificateRevocation = false;
imapClient.ServerCertificateValidationCallback = (s, c, h, e) => true;
imapClient.Connect(_webServiceUrl, _webServicePort, SecureSocketOptions.Auto);
imapClient.Authenticate(new SaslMechanismOAuth2(_userMail, authenticationResult.AccessToken));
if (string.IsNullOrEmpty(_folder))
{
oFolder = imapClient.Inbox;
}
else
{
oFolder = imapClient.GetFolder(_folder);
}
oFolder.Open(FolderAccess.ReadWrite);
In fact I want to be able to authenticate using the tenanid, client secret and clientid but without the interactive mode (as the app is a windows services).
So I tried to use another code with the tenantid, clientSecret and ClientId but I receive the "Authentication failed" error message :
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(_clientId)
.WithClientSecret(_clientSecret)
.WithRedirectUri("http://localhost")
.WithAuthority(new Uri("https://login.microsoftonline.com/" + _tenantid + "/"))
.Build();
var scopes = new[] { "https://outlook.office365.com/.default" };
var authenticationResult = confidentialClientApplication.AcquireTokenForClient(scopes);
var authToken = authenticationResult.ExecuteAsync().Result;
var oauth2 = new SaslMechanismOAuth2(_userMail, authToken.AccessToken);
imapClient = new ImapClient(new ProtocolLogger("TEST_IMAP_" + DateTime.Now.ToString("yyyyMMddHHssffff") + ".log"));
imapClient.CheckCertificateRevocation = false;
imapClient.ServerCertificateValidationCallback = (s, c, h, e) => true;
imapClient.Connect(_webServiceUrl, _webServicePort, SecureSocketOptions.Auto);
imapClient.Authenticate(oauth2);
I've the following permission for my app on the Azure portal:
MSGraph
IMAP.AccessAsUser.All
Mail.Read
Mail.ReadWrite
Mail.Send
Did I miss something? I'm afraid it may be impossible? The official sample on Mailkit website use the interactive mode.
Btw, I'm using Mailkit v2.4
Thank you for your help.
It appears that OAUTH2 authentication with Office365 via the non-interactive method is unsupported by the Microsoft Exchange IMAP/POP3/SMTP protocols and that the only way to get access to Office365 mail using the non-interactive method of OAUTH2 authentication is via the Microsoft.Graph API.
I've been getting a lot of questions about this over the past few months and as far as I'm aware, no one (myself included) has been able to find a way to make this work.
I keep hoping to see someone trying to do this (even in another language) here on StackOverflow with an accepted answer. So far, all I've seen are questions about OAuth2 using the interactive approach (which, as you've seen, I have written documentation for and is known to work well with MailKit).

How to use SimpleProvider with my own MSAL C# code

I'm trying to use my own MSAL code to work together. Developed with .NET Core 5 MVC.
I have similar problem as I found in below link. But I just don't know how to make it work with the proposed answer. Or in other words, I'm still confuse how this integration is done.
[It is mandatory to use the login component in order to use the other components]It is mandatory to use the login component in order to use the other components
[Quickstart for MSAL JS]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html
I also have read following article too:
[Simple Provider Example]https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/main/samples/examples/simple-provider.html
[A lap around microsoft graph toolkit day 7]https://developer.microsoft.com/en-us/office/blogs/a-lap-around-microsoft-graph-toolkit-day-7-microsoft-graph-toolkit-providers/
is there someone can pointing to me more details explanation about how to archive this.
Can someone explains further below response further. How to do it. Where should I place the code and how to return AccessToken to SimpleProvider?
Edited:
Update my question to be more precise to what I want besides on top of the question. Below is the code I used in Startup.cs to automatically trigger pop up screen when user using the web app. When using the sample provided, it is always cannot get access token received or userid data. Question 2: How to save or store token received in memory or cache or cookies for later use by ProxyController and its classes.
//Sign in link under _layouts.aspx
<a class="nav-link" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in</a>
// Use OpenId authentication in Startup.cs
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// Specify this is a web app and needs auth code flow
.AddMicrosoftIdentityWebApp(options =>
{
Configuration.Bind("AzureAd", options);
options.Prompt = "select_account";
options.Events.OnTokenValidated = async context =>
{
var tokenAcquisition = context.HttpContext.RequestServices
.GetRequiredService<ITokenAcquisition>();
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(async (request) =>
{
var token = await tokenAcquisition
.GetAccessTokenForUserAsync(GraphConstants.Scopes, user: context.Principal);
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", token);
})
);
// Get user information from Graph
try
{
var user = await graphClient.Me.Request()
.Select(u => new
{
u.DisplayName,
u.Mail,
u.UserPrincipalName,
u.MailboxSettings
})
.GetAsync();
context.Principal.AddUserGraphInfo(user);
}
catch (ServiceException)
{
}
// Get the user's photo
// If the user doesn't have a photo, this throws
try
{
var photo = await graphClient.Me
.Photos["48x48"]
.Content
.Request()
.GetAsync();
context.Principal.AddUserGraphPhoto(photo);
}
catch (ServiceException ex)
{
if (ex.IsMatch("ErrorItemNotFound") ||
ex.IsMatch("ConsumerPhotoIsNotSupported"))
{
context.Principal.AddUserGraphPhoto(null);
}
}
};
options.Events.OnAuthenticationFailed = context =>
{
var error = WebUtility.UrlEncode(context.Exception.Message);
context.Response
.Redirect($"/Home/ErrorWithMessage?message=Authentication+error&debug={error}");
context.HandleResponse();
return Task.FromResult(0);
};
options.Events.OnRemoteFailure = context =>
{
if (context.Failure is OpenIdConnectProtocolException)
{
var error = WebUtility.UrlEncode(context.Failure.Message);
context.Response
.Redirect($"/Home/ErrorWithMessage?message=Sign+in+error&debug={error}");
context.HandleResponse();
}
return Task.FromResult(0);
};
})
// Add ability to call web API (Graph)
// and get access tokens
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
Configuration.Bind("AzureAd", options);
}, GraphConstants.Scopes)
// Add a GraphServiceClient via dependency injection
.AddMicrosoftGraph(options =>
{
options.Scopes = string.Join(' ', GraphConstants.Scopes);
})
// Use in-memory token cache
// See https://github.com/AzureAD/microsoft-identity-web/wiki/token-cache-serialization
.AddInMemoryTokenCaches();
Since you are using MVC, I recommend using the ProxyProvider over the Simple Provider.
SimpleProvider - useful when you have existing authentication on the client side (such as Msal.js)
ProxyProvider - useful when you are authenticating on the backend and all graph calls are proxied from the client to your backend.
This .NET core MVC sample might help - it is using the ProxyProvider with the components
Finally, I have discovered how to do my last mile bridging for these two technology.
Following are the lines of the code that I have made the changes. Since I'm using new development method as oppose by MSAL.NET, a lot of implementation has been simplified, so many of examples or article out there, may not really able to use it directly.
Besides using links shared by #Nikola and me above, you also can try to use below
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/
to consolidate to become your very own solution. Below are the changes I have made to make it worked.
Change in Startup.cs class
// Add application services. services.AddSingleton<IGraphAuthProvider, GraphAuthProvider>(); //services.AddSingleton<IGraphServiceClientFactory, GraphServiceClientFactory>();
Change in ProxyController.cs class
private readonly GraphServiceClient _graphClient;
public ProxyController(IWebHostEnvironment hostingEnvironment, GraphServiceClient graphclient)
{
_env = hostingEnvironment;
//_graphServiceClientFactory = graphServiceClientFactory;
_graphClient = graphclient;
}
Change in ProcessRequestAsync method under ProxyController.cs
//var graphClient = _graphServiceClientFactory.GetAuthenticatedGraphClient((ClaimsIdentity)User.Identity);
var qs = HttpContext.Request.QueryString;
var url = $"{GetBaseUrlWithoutVersion(_graphClient)}/{all}{qs.ToUriComponent()}";
var request = new BaseRequest(url, _graphClient, null)
{
Method = method,
ContentType = HttpContext.Request.ContentType,
};

mvc and web.api authorisation using oauth

Seems like I am asking the same question that many have but can not fit everything together.
I have a standard MVC application configured with Oauth using the standard Identity DB, the user signs in and that all works fine, I then want to call the Web.API application as an authenticated user. From my research I have added the bearer token to the httpclient thinking that this would somehow be authorized in the web.api application I have set the Web.API application to point to the same identity db but im not sure what i am missing. I've spent days on this and no luck so any samples would be much appreciated.
the code to get the bearer token is
protected string GetToken()
{
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, User.Identity.Name));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, User.Identity.GetClaimValue(ClaimTypes.NameIdentifier.ToString())));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
string accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
return accessToken;
}
the call to the Web.APi is
var token = GetToken();
string uri = UriEASOnlineApi + EASOnlineWebAPI.SignErectors;
List<SignErector> result;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var task = await client.GetAsync(uri);
if (task.IsSuccessStatusCode)
{
var jsonString = await task.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<List<SignErector>>(jsonString);
}
else
{
throw new Exception($"failed: {task.StatusCode}");
}
}
First of all you need to know how authentication is done in WebApi. Then if you have working example of call to this WebApi e.g. from Postman or Fiddler you can compare manual request with the one you create programmatically from MVC application.

Linq to Twitter - Bad Authentication data

I've the the latest version of Linq to Twitter (3.1.2), and I'm receiving the "Bad Authentication data" error with the code below:
var auth = new ApplicationOnlyAuthorizer
{
CredentialStore = new InMemoryCredentialStore
{
ConsumerKey = "xxxx",
ConsumerSecret = "xxxx"
}
};
using (var twitter = new TwitterContext(auth))
{
var users = twitter.User.Where(s => s.Type == UserType.Search && s.Query == "filter:verified").ToList();
}
I thought at first that it could be Twitter taking a while to accept my new credentials, but I used Twitter's OAuth tool with my keys, and they produced tokens without issue. Any ideas what I'm missing here?
I could not find a duplicate, as the code referenced # https://stackoverflow.com/questions/16387037/twitter-api-application-only-authentication-with-linq2twitter#= is no longer valid in the version I am running.
That query doesn't support Application-Only authorization. Here's the Twitter docs to that:
https://dev.twitter.com/rest/reference/get/users/search
Instead, you can use SingleUserAuthorizer, documented here:
https://github.com/JoeMayo/LinqToTwitter/wiki/Single-User-Authorization
Like this:
var auth = new SingleUserAuthorizer
{
CredentialStore = new SingleUserInMemoryCredentialStore
{
ConsumerKey = ConfigurationManager.AppSettings["consumerKey"],
ConsumerSecret = ConfigurationManager.AppSettings["consumerSecret"],
AccessToken = ConfigurationManager.AppSettings["accessToken"],
AccessTokenSecret = ConfigurationManager.AppSettings["accessTokenSecret"]
}
};
To find out what type of authorization is possible, you can visit the L2T wiki at:
https://github.com/JoeMayo/LinqToTwitter/wiki
and each API query and command has a link at the bottom of the page to the corresponding Twitter API documentation.

Resources