I am faced with problem when trying to authenticate my app via facebook. After entering a username and password in the facebook form, I caught a NullReferenceException.
I've noticed that it happens only when I compile with Android 6.0.
[assembly: ExportRenderer(typeof(FirstPage), typeof(FirstPageRenderer))]
namespace Dating.Droid.Renderers
{
public class FirstPageRenderer : PageRenderer
{
public FirstPageRenderer()
{
var activity = this.Context as Activity;
if (activity != null)
{
var auth = new OAuth2Authenticator(
clientId: "MyFacebookId", // your OAuth2 client id
scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"),
redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html"));
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
// Authenticated
}
else
{
// The user cancelled
}
};
activity.StartActivity(auth.GetUI(activity));
}
}
}
}
Exception appears on auth.Completed event
Stack Trace: at Dating.Droid.Renderers.FirstPageRenderer+<>c.<.ctor>b__0_0 (System.Object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs eventArgs) [0x00002] in D:\Dev\Real\Xamarin\Dating\Dating.Droid\Renderers\FirstPageRenderer.cs:31
Related
I'm attempting to use Xamarin.Essentials.WebAuthenticator to Authenticate using Azure AD which in turn should call back to my mobile app with an WebAuthenticatorResult. The process works up to the point where the Callback URI should callback into my app.
Command in Mobile App is fired calling the AuthenticateAsync method.
A new web browser opens on the mobile and I am prompted to enter my Microsoft Credentials
Sign in using my organisations user credentials.
Sign in successful.
Error message displays
The callback URI is never fired and the only option I have is to close the browser which then throws an exception in my app, this is expected when the process fails or the user closes the browser. The authentication result is never returned in my app.
What I expect to happen is once the authentication was successful the browser would redirect the browser to the RedirectURI and my mobile app would handle it.
Am I misunderstanding how this is supposed to work or have I misconfigured something?
WebAuthenticator called from my ViewModel
async Task<bool> SSOLogin()
{
ErrorMessage = string.Empty;
try
{
var authRequestUrl = new Uri("https://myapps.microsoft.com/signin/2borno2-1234-abcd-baba-42aaa70ab1da?tenantId=ab12ac17-4321-acbd-1234-72aae60ed1ca6");
var callbackUrl = new Uri("mobile://myapp");
var authResult = await WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions
{
Url = authRequestUrl,
CallbackUrl = callbackUrl,
PrefersEphemeralWebBrowserSession = true
});
var accessToken = authResult?.AccessToken;
return true;
}
catch(Exception e)
{
var msg = e.Message;
}
finally
{
IsBusy = false;
}
return false;
}
Activity to handle callback URI (mobile://myapp)
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionView },
Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
DataScheme = "mobile")]
public class WebAuthenticationCallbackActivity : Xamarin.Essentials.WebAuthenticatorCallbackActivity
{
}
Azure AD Application Setup
N.B. the tenant Id and client ids are not the actual ones I'm using. I'm confident these are working as the browser in the mobile app takes me to the correct log in page and the error message displays the application name I've set up in Azure AD.
The sign-on URL error in your screenshot ("Undefined sign-on url for app") typically indicates that you need to set the Home Page URL under the app registration's branding tab.
From your other screenshot I am unable to tell whether you have selected the checkbox next to the mobile://myapp URL to make it the default. If you haven't done that and have multiple Redirect URIs added, the first one will get selected by default.
In the end I used the Microsoft.Identity.Client nuget package instead and configured the Mobile app platforms in Azure AD.
public class AzureADTokenService
{
// Replace these with your details
readonly string AndroidAppId = "YOUR.APP.NAME";
readonly string GeneratedAndroidAppSignature = "1234567890asdfghjkl";
readonly string iOSAppId = "YOUR.PACKAGE.BUNDLENAME";
readonly string AzureADClientID = "aaaaaaaa-1111-2222-3333-444444444444";
readonly string AzureADTenantID = "aaaaaaaa-1111-2222-3333-444444444444";
private IParentWindowLocatorService _parentWindowLocatorService;
readonly List<string> Scopes = new List<string> { "user.read" };
private IPublicClientApplication _pca;
string RedirectUri
{
get
{
if (DeviceInfo.Platform == DevicePlatform.Android)
return $"msauth://{AndroidAppId}/{GeneratedAndroidAppSignature}";
else if (DeviceInfo.Platform == DevicePlatform.iOS)
return $"msauth.{iOSAppId}://auth";
return string.Empty;
}
}
public AzureADTokenService(IParentWindowLocatorService parentWindowLocatorService)
{
_parentWindowLocatorService = parentWindowLocatorService;
// Create tha application
_pca = PublicClientApplicationBuilder.Create(AzureADClientID)
.WithIosKeychainSecurityGroup("com.microsoft.adalcache")
.WithRedirectUri(RedirectUri)
.WithAuthority(AzureCloudInstance.AzurePublic, AzureADTenantID)
.Build();
}
public async Task<string> SignInAsync()
{
AuthenticationResult result;
try
{
var accounts = await _pca.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
// Attempt to sign in silently using existing cached tokens
if (firstAccount != null)
result = await _pca.AcquireTokenSilent(Scopes, firstAccount).ExecuteAsync();
else
{
var builder = _pca.AcquireTokenInteractive(Scopes)
.WithUseEmbeddedWebView(true);
if (DeviceInfo.Platform == DevicePlatform.Android)
{
var windowLocatorService = _parentWindowLocatorService.GetCurrentParentWindow();
builder = builder.WithParentActivityOrWindow(windowLocatorService);
}
result = await builder.ExecuteAsync();
}
}
catch (MsalUiRequiredException)
{
var builder = _pca.AcquireTokenInteractive(Scopes)
.WithUseEmbeddedWebView(true);
if (DeviceInfo.Platform == DevicePlatform.Android)
{
var windowLocatorService = _parentWindowLocatorService.GetCurrentParentWindow();
builder = builder.WithParentActivityOrWindow(windowLocatorService);
}
result = await builder.ExecuteAsync();
}
if (result == null)
{
return null;
}
return result.AccessToken;
}
public async Task<bool> SignOutAsync()
{
try
{
var accounts = await _pca.GetAccountsAsync();
// Go through all accounts and remove them.
while (accounts.Any())
{
await _pca.RemoveAsync(accounts.FirstOrDefault());
accounts = await _pca.GetAccountsAsync();
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
return false;
}
}
}
I'm working on a basic Nativescript app based on the demo on:
https://github.com/alexziskind1/nativescript-oauth2/blob/master/demo-angular/src/app/auth.service.ts
import { Injectable } from "#angular/core";
import {
TnsOAuthClient,
ITnsOAuthTokenResult
} from "nativescript-oauth2";
#Injectable()
export class AuthService {
private client: TnsOAuthClient = null;
constructor() { }
public tnsOauthLogin(providerType): Promise<ITnsOAuthTokenResult> {
this.client = new TnsOAuthClient(providerType);
return new Promise<ITnsOAuthTokenResult>((resolve, reject) => {
this.client.loginWithCompletion(
(tokenResult: ITnsOAuthTokenResult, error) => {
if (error) {
console.error("back to main page with error: ");
console.error(error);
reject(error);
} else {
console.log("back to main page with access token: ");
console.log(tokenResult);
resolve(tokenResult);
}
}
);
});
}
public tnsOauthLogout() {
if (this.client) {
this.client.logout();
}
}
}
Which gets back the access token.
My question is, how can I get the user info with that library: nativescript-oauth2?
I know that when you have the access token you can get some user info (id, name, email, etc.). For example, with Facebook you can do it in the following way by using the (dummy) access token:
https://graph.facebook.com/me?access_token=EAAF9wRREKB4BANZAZCNzp0nhmY8dgttSicR3u3aOxieEYR0kZAw298lHZAsgIwAeA9n4MeBWKivZBZB0ElFbzvo5N49kpIVozNKWFslcYIssdORsg4hHelxJI05ZBuycBjm6VrDpwWlljXkNCLR8prtdopt4mBWMYbqNouzpfn9JrF6TyXCaa2D4DhZBD4pOCBwZD
Is there any way to get the user info (id, name, email, etc.) with that library nativescript-oauth2?
How do I specify the scope of the fields I want to retrieve?, for example: { name, email, etc } by using nativescript-oauth2?
Thanks!
Can any one help me with this problem?
I don`t know how to use OAuth2 with UWP.
For example, on Andriod code of authentication looks like this:
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]
namespace TestTask.Droid
{
class LoginPageRenderer : PageRenderer
{
private static bool _isShown;
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (_isShown) return;
_isShown = true;
var activity = this.Context as Activity;
var auth = new OAuth2Authenticator(
clientId: "someId",
scope: "",
authorizeUrl: new Uri("https://oauth.vk.com/authorize"),
redirectUrl: new Uri("https://oauth.vk.com/blank.html"));
auth.Completed += (sender, eventArgs) => {
if (eventArgs.IsAuthenticated)
{
AuthInfo.Token = eventArgs.Account.Properties["access_token"].ToString();
AuthInfo.UserID = eventArgs.Account.Properties["user_id"].ToString();
}
else
{
// The user cancelled
}
};
activity?.StartActivity((Intent)auth.GetUI(activity));
}
}
}
so, on android the solution is in this method
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
and in this row fo code
activity?.StartActivity((Intent)auth.GetUI(activity));
My question is: How I can do the same in UWP, or how I can make it work in UWP?
Thank you all! I`ve found a solution for my purpose for UWP platform.
[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]
namespace TestTask.UWP
{
public class LoginPageRenderer : PageRenderer
{
private Windows.UI.Xaml.Controls.Frame _frame;
private bool _isShown;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e)
{
base.OnElementChanged(e);
if (_isShown) return;
_isShown = true;
if (Control == null)
{
WindowsPage windowsPage = new WindowsPage();
var auth = new OAuth2Authenticator(
clientId: "someID",
scope: "",
authorizeUrl: new Uri("https://oauth.vk.com/authorize"),
redirectUrl: new Uri("https://oauth.vk.com/blank.html"));
_frame = windowsPage.Frame;
if (_frame == null)
{
_frame = new Frame();
//_frame.Language = global::Windows.Globalization.ApplicationLanguages.Languages[0];
windowsPage.Content = _frame;
SetNativeControl(windowsPage);
}
auth.Completed += (sender, eventArgs) => {
if (eventArgs.IsAuthenticated)
{
AuthInfo.Token = eventArgs.Account.Properties["access_token"].ToString();
AuthInfo.UserID = eventArgs.Account.Properties["user_id"].ToString();
}
else
{
// The user cancelled
}
};
Type pageType = auth.GetUI();
_frame.Navigate(pageType, auth);
Window.Current.Activate();
}
}
}
}
Please mark it solved!
My question is: How I can do the same in UWP, or how I can make it work in UWP?
I'm not sure whether the Xamarin.Auth supports UWP or not, but for UWP, we can use Web authentication broker for OAuth, you can check the official WebAuthenticationBroker sample, it's not xamarin, but you can code it in native UWP project and use Custom renderer to do the same thing in Xamarin.
Xamarin.Auth supports UWP for Standard/Traditional quite long.
Xamarin.Forms support was recently added and is not tested thoroughly (1.5.0-alpha)
I use the onedrive SDK in a Cross Plattform app. On Windows the Authentication works via the OneDriveClientExtensions.GetClientUsingWebAuthenticationBroker.
Now I'm trying to login on Android. I tried it with this:
oneDriveClient = OneDriveClient.GetMicrosoftAccountClient(
appId: MSA_CLIENT_ID,
returnUrl: RETURN_URL,
scopes: scopes,
clientSecret: MSA_CLIENT_SECRET);
await oneDriveClient.AuthenticateAsync();
But get an error that no valid token could be received. Do I have to implement a own AuthenticationProvider inhereting from WebAuthenticationBrokerAuthenticationProvider who shows a browser for the oauth? Or what would be the way to go here?
I solved this using the Xamarin Auth Component. Heres the code who calls the webview with the login:
private const string RETURN_URL = #"https://login.live.com/oauth20_desktop.srf";
private void ShowWebView()
{
var auth = new OAuth2Authenticator(
clientId: MSA_CLIENT_ID,
scope: string.Join(",", scopes),
authorizeUrl: new Uri(GetAuthorizeUrl()),
redirectUrl: new Uri(RETURN_URL));
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
//Do Something
}
};
var intent = auth.GetUI(Application.Context);
intent.SetFlags(ActivityFlags.NewTask);
Application.Context.StartActivity(intent);
}
private string GetAuthorizeUrl()
{
var requestUriStringBuilder = new StringBuilder();
requestUriStringBuilder.Append("https://login.live.com/oauth20_authorize.srf");
requestUriStringBuilder.AppendFormat("?{0}={1}", Constants.Authentication.RedirectUriKeyName, RETURN_URL);
requestUriStringBuilder.AppendFormat("&{0}={1}", Constants.Authentication.ClientIdKeyName, MSA_CLIENT_ID);
requestUriStringBuilder.AppendFormat("&{0}={1}", Constants.Authentication.ScopeKeyName,
string.Join("%20", scopes));
requestUriStringBuilder.AppendFormat("&{0}={1}", Constants.Authentication.ResponseTypeKeyName,
Constants.Authentication.TokenResponseTypeValueName);
return requestUriStringBuilder.ToString();
}
I managed to log into Facebook with childBrowser plugin without any problems:
function deviceStart() {
FB.init({ appId: "xxxxxxxxxxxxxx", nativeInterface: PG.FB });
};
function onPubFacebookBtn(){ // I call this from a button
var my_client_id = "xxxxxxxxxxxxx",
my_redirect_uri = "http://www.facebook.com/connect/login_success.html",
my_type = "user_agent",
my_display = "touch"
var authorize_url = "https://graph.facebook.com/oauth/authorize?";
authorize_url += "client_id="+my_client_id;
authorize_url += "&redirect_uri="+my_redirect_uri;
authorize_url += "&display="+my_display;
authorize_url += "&scope=publish_stream,user_photos,email,user_online_presence,offline_access"
window.plugins.childBrowser.onLocationChange = facebookLocChanged;
window.plugins.childBrowser.onClose = closed;
window.plugins.childBrowser.showWebPage(authorize_url);
}
function facebookLocChanged(loc){
if (/login_success/.test(loc)) {
var fbCode = loc.match(/code=(.*)$/)[1]
localStorage.setItem('pg_fb_session', JSON.stringify(fbCode));
FB.Auth.setSession(fbCode, 'connected');
window.plugins.childBrowser.close();
}}
When I test if the app is logged in with
function getLoginStatus() {
FB.getLoginStatus(function(response) {
if (response.session) {
alert('logged in');
} else {
alert('not logged in');
}
});
}
it returns "loged in", but when I try to get user ID i get an error saying I need an active access token:
function me() {
FB.api('/me', function(response) {
if (response.error) {
alert(JSON.stringify(response.error));
} else {
var data = document.getElementById('data');
response.data.forEach(function(item) {
var d = document.createElement('div');
d.innerHTML = item.name;
data.appendChild(d);
});
}
});
}
This is a mixture of 2 solutions for loging into facebook of which none works.
Help!?
I have used the following code to get the Facebook user's name, please try once.
params='access_token='+accessToken;
$.get("https://graph.facebook.com/me",params,
function(response){
fbUesrName=response.name;
},"json");
You need to have an application on Facebook to generate access token, then you can get the ID, but it would be in the encrypted form.