FOSOAuthServerBundle - generate manually an access token - ios

I have a symfony2 website and a web service secured via oauth using FOSOAuthServerBundle accessible via an iOS app. On the website I use FOSUserBundle and FOSFacebookBundle.
The only thing I miss is giving the possibility to the user to login with facebook on the iOS app and to return him an access_token for my oauth linked to his user account so that he can access my api like other users.
So basically I want to send the user facebookID and facebook_access_token to my webservice, check that the user is correct (token matches my app) and return an authentication token.
QUESTION: is there an easy way to add a "Facebook" grant_type to FOSOAuthServerBundle ?
I know some people have done this seeing these questions:
Design for Facebook authentication in an iOS app that also accesses a secured web service
Get application id from user access token (or verify the source application for a token)
But they do not explain how, they do not seem to use FOSOauthServerBundle and questions are quite old.
I have tried using this bundle:
https://github.com/TheFootballSocialClub/FSCOAuth2FacebookGrantBundle
but this bundle has been downloaded only 9 times before me and is not fully suited to my app (it considers that a Facebook user username equals his facebookId for instance). So I guess what I want to do is re-implement the same kind of thing my own way.
If anyone has already done that our could provide any guidance it would be very much appreciated.
Thank you

To do this, you have to add a Grant Extensions, see the official document "Adding Grant Extensions" : https://github.com/FriendsOfSymfony/FOSOAuthServerBundle/blob/master/Resources/doc/adding_grant_extensions.md
You can find my FacebookGrantExtension to get a token from a FB access_token :
class FacebookGrantExtension implements GrantExtensionInterface
{
protected $userManager = null;
protected $facebookSdk = null;
public function __construct(UserManager $userManager, \BaseFacebook $facebookSdk)
{
$this->userManager = $userManager;
$this->facebookSdk = $facebookSdk;
}
/**
* #see OAuth2\IOAuth2GrantExtension::checkGrantExtension
*/
public function checkGrantExtension(IOAuth2Client $client, array $inputData, array $authHeaders)
{
if (!isset($inputData['facebook_access_token'])) {
return false;
}
$this->facebookSdk->setAccessToken($inputData['facebook_access_token']);
try {
// Try to get the user with the facebook token from Open Graph
$fbData = $this->facebookSdk->api('/me');
if (empty($fbData) || !isset($fbData['id'])) {
return false;
}
// Check if a user match in database with the facebook id
$user = $this->userManager->findUserBy(array(
'facebookId' => $fbData['id']
));
// If no user found, register a new user and grant token
if (null === $user) {
return false;
}
// Else, return the access_token for the user
else {
return array(
'data' => $user
);
}
} catch(\FacebookApiExceptionion $e) {
return false;
}
}
}
And the config.yml :
my.oauth.facebook_extension:
class: My\CoreBundle\Oauth\FacebookGrantExtension
arguments:
userManager: "#fos_user.user_manager"
facebookSdk: "#fos_facebook.api"
tags:
- { name: fos_oauth_server.grant_extension, uri: 'http://grants.api.mywebsite.com/facebook_access_token' }

Related

How do I connect to Exchange Online using OAuth 2.0 in MailKit?

I have a web application that sends e-mails to users via Exchange Online (Office365) using MailKit and Basic Authentication. Our company is MS partner and therefor is obligated to turn off Basic Authentication for our services by the end of february 2020.
So, I want to use OAuth 2.0 to connect to Exchange Online, similar to this example. In fact, there might be a solution available according to this answer but I'm unable to find anything about it.
Right now I'm playing around with MS Identity Platform v2.0 but I'm unable to figure out how to do it.
Any help would be appriciated.
UPDATE 1
I do not want to send mails on behalf of signed-in users but instead there is a single Office365 user account that shall be used to send mails (notifications and so on) to others.
UPDATE 2
I managed to get a little closer to what I want to do using Microsoft Graph SDK and the Username/Password Provider.
Our user account requires multifactor-authentication and therefor I get an error when using the user's password since I cannot satisfy the second factor. When I'm using an app-password authentication fails because of incorrect password.
UPDATE 3
I switched to mail relaying for now. But I will update this question if I'll ever find an answer to it.
Using the Microsoft.Identity.Client you can generate a token and pass though then authentication using that.
I spotted the below for IMAP, POP3 and SMTP so adapted for my project to get a working solution. Although the example show the interactive method, where as I am was trying to use the the client credentials flow with an app secret.
MailKit - Using OAuth2 With Exchange (IMAP, POP3 or SMTP)
Microsoft - Authenticate an IMAP, POP or SMTP connection using OAuth
From #hB0 comment
Setting up Service Principles via client credentials grant flow (non-interactive)
https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-client-credentials-flow-support-for-pop-and/ba-p/3562963
My choice would be to look into Microsoft Graph API . It is a single endpoint for all Microsoft services including Email. Email specific endpoints document is here
Microsoft provides SDK in different languages to develop client applications using Graph API.
At a high level you would need to do the following.
i) Register an application in Azure Active Directory. See here
ii) Use the Oauth2 'authorization code grant' flow to get a refresh token . See here
iii) Exchange the refresh token for an access token and use the access token to call Microsoft Graph API.
iv) You also need to store the refresh token , if you have use cases where you application needs to perform actions even if the user is offline. In this case make sure you include scope 'offline' in step ii)
I would suggest looking into DotNetOpenAuth or a similar library and reading their samples. You'll probably need to know the Windows Live URLs to use for this if the DotNetOpenAuth library doesn't have them built-in.
Samples can be found here: https://github.com/DotNetOpenAuth/DotNetOpenAuth.Samples
I know this is an old post but with Microsoft progressively rolling modern authentication on all Office 365 tenants. Here's what I cobbled together.
I haven't worked with MFA setups.
I use it to fetch attachments via POP3 from automated mails coming in a mailbox of our tenant, the app runs from a scheduled task so it needs to be able to run without interaction.
First, you need to get the TenantID and ClientID the tenant admin gets when registering an app on the tenant. Credits to #jstedfast for the bootstrap doc to use those informations elegantly.
Then, setup a cache for the authentication token (following this article and the wiki page linked to it).
Then handle the logic whether to use interactive or silent authentication and avoid prompting for sign-in everytime. (straight copy/paste from documentation, but rather than leaving a link that might break...)
I wrapped it all together in a function that I call later to handle the authentication.
private static async Task<AuthenticationResult> GetMSALTokenAsync()
{
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/POP.AccessAsUser.All"
};
var options = new PublicClientApplicationOptions
{
ClientId = Settings.Default.MSALClientId,
TenantId = Settings.Default.MSALTenantId,
RedirectUri = Settings.Default.MSALRedirectURI
};
var storageProperties = new StorageCreationPropertiesBuilder(
Settings.Default.MSALTokenCache,
MsalCacheHelper.UserRootDirectory)
.Build();
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
AuthenticationResult authToken;
try
{
authToken = await publicClientApplication.AcquireTokenSilent(scopes, accounts.First(o => o.Username == Settings.Default.LoginPop)).ExecuteAsync();
return authToken;
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
switch (ex.Classification)
{
case UiRequiredExceptionClassification.None:
break;
case UiRequiredExceptionClassification.MessageOnly:
// You might want to call AcquireTokenInteractive(). Azure AD will show a message
// that explains the condition. AcquireTokenInteractively() will return UserCanceled error
// after the user reads the message and closes the window. The calling application may choose
// to hide features or data that result in message_only if the user is unlikely to benefit
// from the message
try
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
{
// Do nothing. The user has seen the message
}
break;
case UiRequiredExceptionClassification.BasicAction:
// Call AcquireTokenInteractive() so that the user can, for instance accept terms
// and conditions
case UiRequiredExceptionClassification.AdditionalAction:
// You might want to call AcquireTokenInteractive() to show a message that explains the remedial action.
// The calling application may choose to hide flows that require additional_action if the user
// is unlikely to complete the remedial action (even if this means a degraded experience)
case UiRequiredExceptionClassification.ConsentRequired:
// Call AcquireTokenInteractive() for user to give consent.
case UiRequiredExceptionClassification.UserPasswordExpired:
// Call AcquireTokenInteractive() so that user can reset their password
case UiRequiredExceptionClassification.PromptNeverFailed:
// You used WithPrompt(Prompt.Never) and this failed
case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
default:
// May be resolved by user interaction during the interactive authentication flow.
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
}
catch (InvalidOperationException)
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
log.Error("Authentication failed.");
return null;
}
Then you can just roll on with the actual logic to do your stuff with the Exchange server.
private static async Task PopDownloadAsync()
{
using (var client = new Pop3Client())
{
try
{
await client.ConnectAsync(Settings.Default.SrvPop, 995, SecureSocketOptions.SslOnConnect);
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
try
{
var result = await GetMSALTokenAsync();
if (result != null)
{
var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
await client.AuthenticateAsync(oauth2);
}
else
{
throw new AuthenticationException("Something went wrong during authentication...");
}
}
catch (AuthenticationException ex)
{
// do stuff
return;
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
if (client.Capabilities.HasFlag(Pop3Capabilities.UIDL))
{
try
{
// do stuff
}
catch (Pop3CommandException ex)
{
// do stuff
}
catch (Pop3ProtocolException ex)
{
// do stuff
if (!client.IsConnected)
return;
}
catch (Exception e)
{
// do stuff
return;
}
}
if (client.IsConnected)
{
await client.DisconnectAsync(true);
}
}
}

How do I set up an OAuth2RestTemplate with client credentials and automatic token refresh in spring-security-oauth 2.2.1.RELEASE

I am implementing a client that authenticates with OAuth2 against WSO2 and I have serious trouble refreshing the access token, getting 401 UNAUTHORIZED. While I have already found out, what the Spring OAuth2 code does, I do not know why its behavior was changed in 2.2.1.RELEASE and to me it seems plain wrong. Actually using 2.0.14.RELEASE works.
Before I am going to show to you, what I have done and what I have already found out, let me formulate my question:
How am I supposed to realize an OAuth2 client with automatic token refresh with client credentials instead of user credentials?
So here is, what I have implemented so far. The client configures an OAuth2RestTemplate with ResourceOwnerPasswordResourceDetails with isClientOnly flag true, as there are no user sessions. The client session can successfully be established and an access token and a refresh token are set.
#Bean
protected OAuth2ProtectedResourceDetails resource() {
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails() {
#Override
public boolean isClientOnly() {
return true;
}
};
List<String> scopes = new ArrayList<>(2);
scopes.add("write");
scopes.add("read");
resource.setScope(scopes);
resource.setGrantType("password");
resource.setAccessTokenUri(TOKEN_URL);
resource.setClientId(MY_CLIENT_ID);
resource.setClientSecret(MY_CLIENT_SECRET);
resource.setUsername(MY_SERVICE_USER);
resource.setPassword(MY_SERVICE_USER_PW);
return resource;
}
#Bean
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
OAuth2RestTemplate template = new OAuth2RestTemplateWithBasicAuth(resource(), new DefaultOAuth2ClientContext(atr));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new LoggingRequestInterceptor());
template.setInterceptors(interceptors);
template.setRetryBadAccessTokens(true);
return template;
}
So far so good. I have verified that this basically works.
But as soon as the access token expires I frequently run into 401 errors, because the token refresh is not executed. Instead, an ordinary authentication request is carried out, but using the client key and secret instead of user/password. To cut a long story short, I have debugged my way through spring-security-oauth2 into AccessTokenProviderChain#obtainAccessToken and found out, that whether a token refresh request is executed is decided upon in the following bit of code. See on Github
if (resource.isClientOnly() || (auth != null && auth.isAuthenticated())) { // P1
existingToken = request.getExistingToken();
if (existingToken == null && clientTokenServices != null) {
existingToken = clientTokenServices.getAccessToken(resource, auth);
}
if (existingToken != null) {
if (existingToken.isExpired()) {
if (clientTokenServices != null) {
clientTokenServices.removeAccessToken(resource, auth);
}
OAuth2RefreshToken refreshToken = existingToken.getRefreshToken();
if (refreshToken != null && !resource.isClientOnly()) { // P2
accessToken = refreshAccessToken(resource, refreshToken, request);
}
}
else {
accessToken = existingToken;
}
}
}
As you can see at P1, the block is entered if either an authorized user session exists (auth) or the resource is configured as clientOnly. As I do not have users but I am in a linked service scenario, I have isClientOnly() == true && auth == null. But at P2 the final decision upon actually doing the refresh is contraticted by requiring !isClientOnly(). So this effectively bans refresh requests in client only scenarios.
This was the way to go in versions before 2.2.1 and I have found out, that this seems to be a fix to the following Issue. To me this seems plain wrong.
Furthermore, to me the patch appears to break client functionality to fix an actual server misbehavior. As you can see in the issue discussion, I have already commented the there. But as that issue is closed and the spring-security-oauth2 forum states that discussions should be held here on StackOverflow, I am asking for help here.
Again the question: How should a client application be configured to consume OAuth2 secured services via OAuth2RestTemplate and an access token runtime of an hour and refresh token runtime of lets say two hours.

IdentityServer4: How to include email in access_token without the client explicitly requesting it?

when the user logs in on the IdentityServer4 via Google, I'd like to access the email (and maybe their google-id) but without having the client request it. So it should be accessible every time, so I can put it in the access_token (because our API needs the user's email address).
I've been injecting into the IProfileService, also the IClaimsService but I can't find the email there. Would it be possible to hook into the Google-SignIn Callback so I can access the response manually?
Thanks!
I solved it by adding the claims I needed in AccountController.ExternalLoginCallback like this:
//Add E-Mail claim even if client didn't ask for it
if (claims.Exists(c => c.Type.Equals(ClaimTypes.Email))) {
additionalClaims.Add(new Claim(JwtClaimTypes.Email, claims.FirstOrDefault(x => x.Type.Equals(ClaimTypes.Email)).Value));
}
then I added the claim to the access_token by dependency injecting my ProfileService class and adding the claims in the MyProfileService.GetProfileDataAsync like this:
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = new List<Claim>();
Claim emailClaim = context.Subject.Claims.Where<Claim>(claim => claim.Type.Equals(JwtClaimTypes.Email)).FirstOrDefault();
if (emailClaim != null)
{
claims.Add(emailClaim);
}
context.IssuedClaims = claims;
return Task.FromResult(0);
}

When using Twitter OAuth with TweetSharp I keep getting asked to authorize my app every time a user wants to log in

I'm trying to implement OAuth with twitter so my users can log into my site using their Twitter IDs. For this I am using the TweetSharp library. Following the examples they have I wrote the following code which seems to work.
public ActionResult Login(string oauth_token, string oauth_verifier)
{
var service = new TwitterService(consumerKey, consumerSecret);
if (oauth_token == null)
{
var requestToken = service.GetRequestToken(Request.Url.ToString());
var uri = service.GetAuthorizationUri(requestToken);
return new RedirectResult(uri.ToString(), false);
}
else
{
var requestToken = new OAuthRequestToken { Token = oauth_token };
OAuthAccessToken accessToken = service.GetAccessToken(requestToken, oauth_verifier);
service.AuthenticateWith(accessToken.Token, accessToken.TokenSecret);
TwitterUser user = service.VerifyCredentials(new VerifyCredentialsOptions());
TempData["response"] = string.Format("Your username is {0}", user.ScreenName);
return RedirectToAction("Success");
}
}
public ActionResult Success()
{
ViewBag.Response = TempData["response"];
return View();
}
However, there is a problem. Every time the user logs into the system twitter asks them to authorize the application even though they have done it before. Is there a way to prevent this behavior? I have also implemented OAuth with Facebook and Google and I don't need to authorize the application every time I want to log into the system.
Unfortunately not as far as I'm aware. Twitter uses OAuth 1.1 rather than OAuth 2.0 like Facebook and Google, so there is a manual step in the middle in which users are asked to authorise the application even though they have done already. I'm having exactly the same issue and it appears to be something we have to live with.
I was having the same problem with a slightly older app I was trying to resurrect. I noticed that the problem went away when I selected "Sign in with Twitter" in my app settings at http://dev.twitter.com/apps.

DotNetOpenAuth Authentication against Google Apps OpenID using MVC

I'm trying to approximate Single Sign on. Currently, the most workable solution involves the user imputing details on my site before being sent off to goggle Apps to authenicate.
I'm using dotnetopenauth to send and recieve the requests
This means that it takes no account of google apps login cookies should they be present.
There are several questions covering this already. But none seem to have an answer to this.
This is the current code (note that I'm constraining the possible domains that can be used to just google apps for my domain):
public ActionResult Authenticate(string returnUrl)
{
string input=Request.Form["openid_identifier"]+"#example.com";
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(input, out id))
{
try
{
return openid.CreateRequest(input).RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("Login");
}
}
else
{
ViewData["Message"] = "Invalid identifier";
return View("Login");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
var results = userstable.Select(response.FriendlyIdentifierForDisplay);
if (results.Count() > 0)
{
Session["FriendlyIdentifier"] = results.ElementAt(0).UserFName;
return RedirectToAction("Index", "Home", results.ElementAt(0).UserID);
}
else
{
UsersDataModel user = new UsersDataModel();
user.OpenID = response.ClaimedIdentifier.ToString();
user.UserID = Utils.HashToBase64(response.FriendlyIdentifierForDisplay);
user.Type = "Empolyee";
userstable.Insert(user);
//return RedirectToAction("Register");
}
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("Login");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("Login");
}
}
return new EmptyResult();
}
The only difference between this code and the normal pattern are the following lines:
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
These lines correctly setup OpenId to process the response from google apps.
As I said, this works only if the user inputs his/her email address first. i can't find a way of automatically redirecting to the Google Apps Login
In normal gmail, if I pass the user off to: https://www.google.com/accounts/o8/id instead of processing the input, google will have the user input login details on their site rather than on mine. It will skip that if there are cookies and just pass the user right back to my site with no fuss.
I would like to have the same behavior for my site for the google apps logins.
Additionally, it would be nice if I could constrain the logins to my google apps domain.
There are a couple of URLs floating around:
https://www.google.com/accounts/o8/site-xrds?hd=example.com
or
https://www.google.com/a/example.com/o8/id
But even with those lines of code I pointed out earlier, dontnetopenauth refuses to see an openid endpoint at either address.
Even the sample in dotnetaopenauth for webforms still requires user input.
Any help would be most appreciated.
You can't have no user input and any Google Apps Domain work for the user, because the user may be logged into 15 Google Apps for Domains accounts, and then no one would know which account the user means to log in with.
But since you say you'd like them to only be able to log in with your own domain, that's easy. Skip asking the user, and just pretend "example.com" was the user input and pass that into DotNetOpenAuth. It doesn't have to be an email address. Only the domain name is used anyway.
That said, that doesn't guarantee no one from other Providers or domains can log into your site. To do that, you need to filter on the IAuthenticationResponse that comes back and make sure its Provider property matches the one value you intend to allow. Otherwise, "unsolicited assertions" can still come in.

Resources