I'm developing a Sinatra app for which I'd like to use OmniAuth. So far, I have something similar to this for the web app:
http://codebiff.com/omniauth-with-sinatra
I'd like the web app to be usable via Android phones which would use an API, authenticating by means of a token. The development of an API seems to be covered nicely here:
Sinatra - API - Authentication
What is not clear is now I might arrange the login procedure. Presumably it would be along these lines:
User selects what service to use, e.g. Twitter, FaceBook &c., by means of an in-app button on the Android device.
The Android app opens a webview to log in to the web app.
A token is somehow created, stored in the web app's database, and returned to the Android app so that it can be stored and used for subsequent API requests.
I'm not very clear on how point 3 might be managed - does anyone have any suggestions?
As no-one seems to have any suggestions, here's what I've come up with so far. I don't think it's very good, though.
I've added an API key to the user model, which is created when the user is first authenticated:
class User
include DataMapper::Resource
property :id, Serial, :key => true
property :uid, String
property :name, String
property :nickname, String
property :created_at, DateTime
property :api_key, String, :key => true
end
....
get '/auth/:name/callback' do
auth = request.env["omniauth.auth"]
user = User.first_or_create({ :uid => auth["uid"]},
{ :uid => auth["uid"],
:nickname => auth["info"]["nickname"],
:name => auth["info"]["name"],
:api_key => SecureRandom.hex(20),
:created_at => Time.now })
session[:user_id] = user.id
session[:api_key] = user.api_key
flash[:info] = "Welcome, #{user.name}"
redirect "/success/#{user.id}/#{user.api_key}"
end
If the authorisation works then the api_key is supplied to the Android app, which will presumably store it on the device somewhere:
get '/success/:id/:api_key', :check => :valid_key? do
user = User.get(params[:id],params[:api_key])
if user.api_key == params[:api_key]
{'api_key' => user.api_key}.to_json
else
error 401
end
end
All API calls are protected as in the link in my original post:
register do
def check (name)
condition do
error 401 unless send(name) == true
end
end
end
helpers do
def valid_key?
user = User.first(:api_key => params[:api_key])
if !user.nil?
return true
end
return false
end
end
For public use I'll only allow SSL connections to the server. Any suggestions for improvement would be welcome.
Related
I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}
I have an issue with react-native-firebase (or firebase) in which my app does not receive a trigger after the auth token refreshes. It's pretty much the same issue as [1], but they never posted a solution.
So, what happens is that both on an Android phone and on the Android emulator (no idea about iOS), signing up, logging in and logging out works perfectly, meaning the listeners correctly see when I do a logout() etc. But the listeners never fire when the token refreshes.
My first question is: Am I correct to assume that the onIdTokenChanged-listener should automatically fire after 60 minutes without having to do anything else, e.g. call any firebase function, such that the app just sits there doing nothing for 60 minutes and then receiving the event and replacing the token?
My main component which contains the listeners looks like this:
class ReduxAppWrapper extends Component {
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
console.log('COMP DID MOUNT: AUTH STATE CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onIdTokenChanged((user) => {
console.log('COMP DID MOUNT: TOKEN CHANGED! ' + JSON.stringify(user));
});
firebase.auth().onUserChanged((user) => {
console.log('COMP DID MOUNT: USER CHANGED! ' + JSON.stringify(user));
});
};
render() {
return (
<ReduxProvider store={store}>
<MenuProvider>
<PaperProvider>
<AppContainer />
</PaperProvider>
</MenuProvider>
</ReduxProvider>);
}
}
Normally inside the listener I have a function that dispatches a redux-action such that the authentication information is broadcast across my components. Inside those components I use the jwt token for http-requests to my backend.
Now the backend of course uses firebase to validate that token (and this is where the problem occurs after the 60 minutes since it retrieves an outdated jwt), but I think I am right to assume that the problem lies within the app since the refresh does not happen.
I'd be really glad if someone could point me to where to look, I also tried to find out in the firebase console whether a token refresh event was sent, but I could not find anything about that.
So basically:
1) Am I right to assume that the firebase.auth().onIdTokenChanged() function should be called without me doing anything else? Or is it not enough to define the listener once in the main component (also regarding the fact that other screens will be rendered on top of that due to the stack-nvigation).
2) If the code is fine, do you have any hints for where to look?
Thanks so much!
[1] https://github.com/invertase/react-native-firebase/issues/531
For anyone with the same issue, I ended up asking firebase asking for the token everytime I needed it. I still think this should not be necessary but I did not want to spend any more time analyzing why the refresh did not work automatically. So what I am doing in the app is
firebase.auth().currentUser.getIdToken().then((token) => {
fetch(url, {
method: 'GET',
headers: { Authorization: token }
})
}
¯\_(ツ)_/¯
Apparently, with getIdToken, Firebase only makes a call to its server to get a new token if the current token has expired; it does not create unnecessary requests if it does not have to.
Quite a crucial detail which can be confusing if you are not aware of it and makes you (rightfully) assume that onIdTokenChanged is a listener which you would need to use to automatically update the token ...
https://firebase.google.com/docs/reference/js/firebase.User.html#getidtoken
Returns the current token if it has not expired. Otherwise, this will refresh the token and return a new one.
I want to verify user profile from Google Authorization code sent by android client, to do that, we have to download client_secrets.json and put it inside our rails app. Just like this tutorial https://developers.google.com/identity/protocols/OAuth2WebServer
But when I try to follow this step
require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'
client_secrets = Google::APIClient::ClientSecrets.load
auth_client = client_secrets.to_authorization
auth_client.update!(
:scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
:redirect_uri => 'http://www.example.com/oauth2callback',
:additional_parameters => {
"access_type" => "offline", # offline access
"include_granted_scopes" => "true" # incremental auth
}
)
Rails throws an error said "No client_secrets.json filename supplied and/or could not be found in search path."
The errors shows up even though I have insert client_secrets.json inside config/client_secrets.json
Do you know what's the problem or what's the alternative for this solution, thank you.
It seems, ClientSecrets.load accepts optional argument which is config filename path. So I believe it's okay if you specify filename directly:
Google::APIClient::ClientSecrets.load("#{Rails.root}/config/client_secrets.json")
I've been wracking my brain these past two days to try and understand how to use the authentication built into ASP.NET's WebAPI 2 using Google as an external authentication, and not being familiar with OAuth 2, I'm quite lost. I have followed this tutorial to set up the sign-in button on my Android client and send the "idToken" to the Web API. I've also followed this (now out of date) tutorial on setting up Google as an external login.
The problem happens when I try to send it I get {"error":"unsupported_grant_type"} as a response. Some other tutorials lead me to believe that the POST to mysite.com/token does not contain the correct data. This means I'm either building the request incorrectlyon the client, I'm somehow handling it incorrectly on the backend, I'm sending it to the wrong url, or I'm doing something entirely else wrong.
I found this SO answer which says to get a URL from /api/Accounts/ExternalLogins, but the sign-in button already gives me the access token that would supply to me (if I understand that correctly).
If someone could help me out here on what the exact process should be from start to finish, that would be amazing.
UPDATE: Okay, so here are some things that I've learned since I asked this question.
website.com/token URI is the redirect for the built in OAuth server in the WebAPI2 template. This is not useful for this particular problem.
The id_token is an encoded JWT token.
The website.com/signin-google URI is the redirect for normal Google login, but does not accept these tokens.
I may have to write my own AuthenticationFilter that uses the Google Client library to authorize through the Google API.
UPDATE 2: I'm still working on getting this AuthenticationFilter Implementation. Things seem to be going well at this point, but I'm getting stuck on some things. I've been using this example to get the token verification code, and this tutorial to get the AuthenticationFilter code. The result is a mix of both of them. I'll post it here as an answer once it's complete.
Here are my current problems:
Producing an IPrincipal as output. The verification example makes a ClaimPrincipal, but the AuthenticationFilter example code uses a UserManager to match the username to an existing user and returns that principal. The ClaimsPrincipal as created in the verification example directly does not auto-associate with the existing user, so I need to attempt to match some element of the claims to an existing user. So how do I do that?
I still have an incomplete idea of what a proper flow for this is. I'm currently using the Authentication header to pass my id_token string using a custom scheme: "goog_id_token". The client must send their id_token for every method called on the API with this custom AuthenticationFilter. I have no idea how this would usually be done in a professional environment. It seems like a common enough use case that there would be tons of information about it, but I haven't seen it. I have seen the normal OAuth2 flow, and since I'm only using an ID Token, and not an Access Token I'm a bit lost on what an ID Token is supposed to be used for, where it falls in a flow, and where it's supposed to live in an HTTP packet. And because I didn't know these things, I've kind of been making it up as I go along.
Wow, I did it. I figured it out. I... I can't believe it.
As metioned in my question Update 2, this code is assembled from Google's official API C# example and Microsoft's Custom AuthenticationFilter tutorial and code example. I'm going to paste the AuthorizeAsync() here and go over what each block of code does. If you think you see an issue, please feel free to mention it.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
bool token_valid = false;
HttpRequestMessage request = context.Request;
// 1. Look for credentials in the request
//Trace.TraceInformation(request.ToString());
string idToken = request.Headers.Authorization.Parameter.ToString();
The client adds the Authorization header field with the scheme followed by a single space, followed by the id token. It looks something like Authorization: id-token-goog IaMS0m3.Tok3nteXt.... Putting the ID token in the body as given in the google documentation made no sense in this filter so I decided to put it in the header. For some reason it was difficult to pull custom headers from the HTTP packets so I just decided to use the Authorization header with a custom scheme followed by the ID token.
// 2. If there are no credentials, do nothing.
if (idToken == null)
{
Trace.TraceInformation("No credentials.");
return;
}
// 3. If there are credentials, but the filter does not recognize
// the authentication scheme, do nothing.
if (request.Headers.Authorization.Scheme != "id-token-goog")
// Replace this with a more succinct Scheme title.
{
Trace.TraceInformation("Bad scheme.");
return;
}
This whole point of a filter is to ignore requests that the filter doesn't govern (unfamiliar auth schemes, etc), and make judgement on requests that it's supposed to govern. Allow valid authentication to pass to the downstream AuthorizeFilter or directly to the Controller.
I made up the scheme "id-token-goog" because I had no idea if there was an existing scheme for this use case. If there is, somebody please let me know and I'll fix it. I guess it doesn't particularly matter at the moment as long as my clients all know the scheme.
// 4. If there are credentials that the filter understands, try to validate them.
if (idToken != null)
{
JwtSecurityToken token = new JwtSecurityToken(idToken);
JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
// Configure validation
Byte[][] certBytes = getCertBytes();
Dictionary<String, X509Certificate2> certificates =
new Dictionary<String, X509Certificate2>();
for (int i = 0; i < certBytes.Length; i++)
{
X509Certificate2 certificate =
new X509Certificate2(certBytes[i]);
certificates.Add(certificate.Thumbprint, certificate);
}
{
// Set up token validation
TokenValidationParameters tvp = new TokenValidationParameters()
{
ValidateActor = false, // check the profile ID
ValidateAudience =
(CLIENT_ID != ConfigurationManager
.AppSettings["GoogClientID"]), // check the client ID
ValidAudience = CLIENT_ID,
ValidateIssuer = true, // check token came from Google
ValidIssuer = "accounts.google.com",
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
CertificateValidator = X509CertificateValidator.None,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
return identifier.Select(x =>
{
// TODO: Consider returning null here if you have case sensitive JWTs.
/*if (!certificates.ContainsKey(x.Id))
{
return new X509SecurityKey(certificates[x.Id]);
}*/
if (certificates.ContainsKey(x.Id.ToUpper()))
{
return new X509SecurityKey(certificates[x.Id.ToUpper()]);
}
return null;
}).First(x => x != null);
},
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromHours(13)
};
This is all unchanged from the Google example. I have almost no idea what it does. This basically does some magic in creating a JWTSecurityToken, a parsed, decoded version of the token string, and sets up the validation parameters. I'm not sure why the bottom portion of this section is in it's own statement block, but it has something to do with the CLIENT_ID and that comparison. I'm not sure when or why the value of CLIENT_ID would ever change, but apparently it's necessary...
try
{
// Validate using the provider
SecurityToken validatedToken;
ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);
if (cp != null)
{
cancellationToken.ThrowIfCancellationRequested();
ApplicationUserManager um =
context
.Request
.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
Get the user manager from the OWIN context. I had to dig around in context intellisense until I found GetOwinCOntext(), and then found that I had to add using Microsoft.Aspnet.Identity.Owin; in order to add the partial class that included the method GetUserManager<>().
ApplicationUser au =
await um
.FindAsync(
new UserLoginInfo(
"Google",
token.Subject)
);
This was the very last thing I had to fix. Again, I had to dig through um Intellisense to find all of the Find functions and their overrides. I had noticed from the Identity Framework-created tables in my database that there is one called UserLogin, whose rows contain a provider, a provider key, and a user FK. The FindAsync() takes a UserLoginInfo object, which contains only a provider string and a provider key. I had a hunch that these two things were now related. I had also recalled that there was a field in the token format that included a key-looking field that was a long number that started with a 1.
validatedToken seems to be basically empty, not null, but an empty SecurityToken. This is why I use token instead of validatedToken. I'm thinking there must be something wrong with this, but since the cp is not null, which is a valid check for a failed validation, it makes enough sense that the original token is valid.
// If there is no user with those credentials, return
if (au == null)
{
return;
}
ClaimsIdentity identity =
await um
.ClaimsIdentityFactory
.CreateAsync(um, au, "Google");
context.Principal = new ClaimsPrincipal(identity);
token_valid = true;
Here I have to create a new ClaimsPrincipal since the one created above in validation is empty (apparently that's correct). Took a guess on what the third parameter of CreateAsync() should be. It seems to work that way.
}
}
catch (Exception e)
{
// Multiple certificates are tested.
if (token_valid != true)
{
Trace.TraceInformation("Invalid ID Token.");
context.ErrorResult =
new AuthenticationFailureResult(
"Invalid ID Token.", request);
}
if (e.Message.IndexOf("The token is expired") > 0)
{
// TODO: Check current time in the exception for clock skew.
Trace.TraceInformation("The token is expired.");
context.ErrorResult =
new AuthenticationFailureResult(
"Token is expired.", request);
}
Trace.TraceError("Error occurred: " + e.ToString());
}
}
}
}
The rest is just exception catching.
Thanks for checking this out. Hopefully you can look at my sources and see which components came from which codebase.
I am making a native Android app. I have a WordPress website with a few users. I want my WordPress user to log in my app using the same login and password. I would like a very fluent login process without any webview or web browser.
Is it possible ? What would you recommend ?
Yes it is possible. All actions you want to preform from app side, i recommend to add those actions as ajax functions on wordpress side (it is much cleaner and organized that way - and you can base your communication on json like that ). Regarding logic, you can achieve it using two different ways:
Lets say that you want to implement an application which will allow user to, register, login, and create posts.
1st way
class App {
public function __construct(){
add_action( 'wp_ajax_nopriv_registerUser', array( $this, 'registerUser' ) );
add_action( 'wp_ajax_nopriv_loginUser', array( $this, 'loginUser' ) );
add_action( 'wp_ajax_nopriv_createPost', array( $this, 'createPost' ) );
}
public function registerUser() {
//obviusly, you dont need any kind of auth over here
}
public function loginUser() {
$username = //collect,sanitize data from post
$password = //collect data from post
$user = get_user_by( 'login', $username );
if( wp_check_password( $password, $user->user_pass ) ) {
//preform what you need to preform
//construct successful result array
}
else {
//populate result array with an error or something
}
wp_send_json( $result );
}
public function createPost() {
$username = //collect,sanitize data from post
$password = //collect data from post
//collect rest of the data related with post by itself
$user = get_user_by( 'login', $username );
if( wp_check_password( $password, $user->user_pass ) ) {
//insert post, construct successfully array
}
else {
//populate result array with an error or something
}
wp_send_json($result);
}
}
As you can see in example above, you can register as much ajax functions as you want and within each of those you can have user check and password check which is going to work just fine. Point is that, for each action you want to preform from your mobile app, you will need to send an username/password (you can store these on first successful login on app side) with other parameters.
2nd way
Same example above, with one difference. You can (during first login request from app side), on php side generate unique guid which you can save inside wp database, and return it to your app side (where you can save it on your app side). For each next request, you can send that guid and username from app side, confirming that user is already login and can preform privileged actions. There is no a lot of difference (from perspective of performance, I think that in both of the cases you will need to execute one query only - from perspective of auth, you can preform some testing in order to confirm this WP_User class ). Second method is valuable if you need to identify different login sessions from one end to another (if application crashed and you want to check on your side does user want to preform login while he/she is already "logged in", or you want to forbid login in from more then one application per user. )