//
// ContentView.swift
// Test
//
// Created by Toni Nichev on 1/3/24.
//
import SwiftUI
// Our observable object class
class GameSettings: ObservableObject {
@Published var scoree = 0
var test = 4
}
// A view that expects to find a GameSettings object
// in the environment, and shows its score.
struct ScoreView: View {
// 2: We are not instantiating gameSetting here since it's already done in ContentView.
@EnvironmentObject var gameSettings: GameSettings
var body: some View {
Text("Score: \(gameSettings.scoree)")
Text("Test: \(gameSettings.test)")
}
}
struct ContentView: View {
// 1: We instantiate GameSettings only here and pass it to the environmentObject at the end
@StateObject var gameSettings = GameSettings()
var body: some View {
NavigationStack {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button("Increase score") {
gameSettings.scoree += 1
gameSettings.test += 1
}
NavigationLink {
ScoreView()
} label: {
Text("Show score")
}
}
}
.environmentObject(gameSettings)
}
}
#Preview {
ContentView()
}
#Preview {
ScoreView().environmentObject(GameSettings())
}
The goal: setting up GraphQL server using Gqlgen library.
We are going to set up GraphQL server with Users, and create queries to retrieve the users by id or user name.
Schema-first approach – means that instead of using library apis (code-first approach) we are going to write our schema manually using the GraphQL schema definition language.
Setting up the project
Create project directory mkdir gqlgen-tutorial
Navigate to the folder cd gqlgen-tutorial
Initialize go project. go mod init gqlgen-tutorial
Initializing Gqlgen with boilerplate schema and resolvers
gqlgen has handy command to Initialize gqlgen config and generate the models.
go run github.com/99designs/gqlgen init
This will create server.go the server starting point and ./graph directory with a couple of files including schema.graphqls and if you open it you will see that it comes with pre-defined example schema which we are going to remove later and start from scratch.
graph/model/model_gen.go – is auto generated file containing structure of defined by the schema file graph/schema.graphqls
graph/generated.go – this is a file with generated code that injects context and middleware for each query and mutation.
We should not modify these files since they will be modified by gqlgen as we update the schema. Instead, we should edit these files:
graph/schema.graphqls – GraphQL schema file where types, queries, and mutations are defined.
graph/resolver.go – resolver functions for queries and mutations defined in schema.graphqls
At this point we could start the server and see the playground and the schema.
go run ./server.go
Sometimes you might see an error and in this case just run go mod tidy again.
Defining queries
First let’s remove boilerplate schema from graph/schema.graphqls and add our new schema
graph/schema.graphqls
# GraphQL schema example
#
# https://gqlgen.com/getting-started/
type User {
id: ID!
name: String!
userType: String!
}
type Query {
getUser(id:ID!): User
}
input NewUser {
userId: String!
userName: String!
userType: String!
}
type Mutation {
createUser(input: NewUser!): User!
}
Generate code and running the API
We will now generate code, which will update the following files using the information we provided in the schema file:
schema.resolvers.go
model/models_gen.go
generated/generated.go
Delete the example code in schema.resolvers.go and then run the following command:
go run github.com/99designs/gqlgen generate
If we run the server we will run into an error because we didn’t define any resolver yet.
Defining the backend to fetch and store values
In resolver.go:
– import qlgen-tutorial/graph/model. line: 3
– declare a Hash Map that we will use to store users. Line 10
package graph
import "gqlgen-tutorial/graph/model"
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct{
UsersStore map[string]model.User
}
We defined UserStore of type map which essentially is a hash-map with keys of type string and values of type model.User.
In schema.resolvers.go, we are going to modify the boilerplate methods: CreateUser and GetUser
package graph
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
// Code generated by github.com/99designs/gqlgen version v0.17.24
import (
"context"
"fmt"
"gqlgen-tutorial/graph/model"
)
// CreateUser is the resolver for the createUser field.
func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
// create new user to be added in r.Resolver.UsersStore and returned
var user model.User
if len(r.Resolver.UsersStore) == 0 {
// create new UserStore hash map if it does not exist
r.Resolver.UsersStore = make(map[string]model.User)
}
// set up new user attributes
user.ID = input.UserID
user.Name = input.UserName
user.UserType = input.UserType
// adds newly created user into the resolver's UserStore
r.Resolver.UsersStore[input.UserID] = user
return &user, nil
}
// GetUser is the resolver for the getUser field.
func (r *queryResolver) GetUser(ctx context.Context, id string) (*model.User, error) {
fmt.Println(r.Resolver.UsersStore)
// retrieve user from the store
user, isOk := r.Resolver.UsersStore[id]
if !isOk {
return nil, fmt.Errorf("not found")
}
return &user, nil
}
// Mutation returns MutationResolver implementation.
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
// Query returns QueryResolver implementation.
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
GetUser is pretty straight forward: it gets user id and returns the user from the hash map store.
CreateUser first checks if UserStore is initialized. Line 19. If CreateUser length is 0 it initializes the hash-map. Line 21.
Then it gets the values for username, id and userType from the input parameter and sets up the new user, stores it into the UserStore, line 29, and returns it.
Testing
Creating user
Mutation:
mutation createUserMutation($input: NewUser!) {
createUser(input: $input) {
name
id
}
}
with query parameters indicating the target application and redirect page.
2. Authorization Grant
The sign-in.php script initiates the OAuth2 process:
Redirects the user to the OAuth provider (e.g., Google, Facebook).
Handles scopes and client credentials as per the provider’s requirements.
3. Token Exchange
After successful authorization by the user:
The OAuth provider redirects the user back to the callback.php or callback-google.php scripts, including an authorization code (or token) in the query string.example: https://toninichev.com/examples/oauth/oauth-service/callbacks/callback.php?provider=google&state=123&code=4%2F0AanRRruVnGL8TtqeJoQOuaQSPCW1lOGHA2Y15m_uva9M8Cj75Tb08Y81pPW2H147acXRaA&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&prompt=none
The scripts exchange the authorization code for an access token, typically using the POST method with the client secret.
4. Returning to the Application
The token is returned to the frontend:
Via query parameters in the URL after the server-side processing.
This way after clicking on log-in button user is redirected to the sign-in page with all sign-in providers to choose from. Clicking on “continue with …” redirects to provider’s web ui and then redirects back to out web app.
The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)
P A H N
A P L S I I G
Y I R
And then read line by line: "PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
It’s take the first example string: “PAYPALISHIRING”
An array representation of the string would be done by pushing each character into the array.
Then if we want to iterate through this array in zig zag pattern with 3 rows, it will look like this:
Then, we have to create 3 arrays representing the 3 rows as follows:
Dots represent empty strings which we have to ignore before concatenating all 3 rows, convert them to string and this is the answer to this problem.
But how to create the loop to traverse through the zig zag pattern (figure 1) ?
first we set up `headingDown` flag to determine if we are going down or up
each time when the row reaches numRows we are flipping the value of headingDown
when headingDown is false (meaning that we are heading up) we have to do it diagonally: row — , col ++
when row become 0 we are flipping again headingDown and it becomes true again: row ++ and we keep storing values of every character we traversed in the result array.
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function(s, numRows) {
// if we don't have any rows or there is an empty string just return back empty string
if(s.length === 0 || numRows === 0)
return '';
// if rows are numRows is just one, we return the same string
if(numRows === 1) {
return s;
}
var l = s.length;
// put the string into single dimension array to iterate through
var arr = s.split('');
var rowsArray = [];
var row = 0;
var col = 0;
var headingDown = true; // this determines if the cursor is heading down in the same column, or up leaping to the next column (dizgonal)
// instantiate numRows arrays to store values of each row
for(var i = 0; i < numRows; i ++) {
rowsArray[i] = [];
}
// loop through each element in arr and fill rowsArray ()
for(var i = 0; i < l; i ++) {
rowsArray[row][col] = arr[i];
if(headingDown) {
row ++;
}
else {
row --;
col ++;
}
if(row == numRows -1) {
headingDown = false;
} else if(row == 0) {
headingDown = true;
}
}
// Read 2D array and assemble the string
var result = [];
for(var i = 0; i < numRows; i ++) {
for(var j = 0; j < rowsArray[i].length; j ++) {
if(typeof rowsArray[i][j] != 'undefined') {
result.push(rowsArray[i][j]);
}
}
}
return result.join('');
};
convert('PAYPALISHIRING', 3);
convert('AB', 1);
//convert('ABC', 1);
How can we optimize this ?
the above example is good to read but not a good practical example. First we don’t need two dimensional array to store characters for each row. We could replace this with string array and just keep adding characters till we reached the end of the string. This way we also don’t need to keep track of cols
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function(s, numRows) {
// if we don't have any rows or there is an empty string just return back empty string
if(s.length === 0 || numRows === 0)
return '';
// if rows are numRows is just one, we return the same string
if(numRows === 1) {
return s;
}
var l = s.length;
// put the string into single dimension array to iterate through
var arr = s.split('');
var left = 0;
var arrStrings = [];
var row = 0;
var col = 0;
var headingDown = true; // this determines if the cursor is heading down in the same column, or up leaping to the next column (dizgonal)
// instantiate numRows arrays to store values of each row
for(var i = 0; i < numRows; i ++) {
arrStrings[i] = '';
}
// loop through each element in arr and fill arrStrings ()
for(var i = 0; i < l; i ++) {
//arrStrings[row][col] = arr[i];
if(headingDown) {
arrStrings[row] += arr[i];
row ++;
}
else {
arrStrings[row] += arr[i];
row --;
col ++;
}
if(row == numRows -1) {
headingDown = false;
} else if(row == 0) {
headingDown = true;
}
}
var result = '';
// combine all strings and return as one
for(var i = 0; i < numRows; i ++) {
result += arrStrings[i];
}
return result;
};
This tutorial demonstrates how to use oAuth to sign-in with Apple without using any library.We simply use HTTP GET request to their endpoint URL: appleid.apple.com/auth/authorize.
Just to space it up a bit, we are opening Sign-in popup in modal window using window.open but this could be done in the same window. The advantages of open it in modal window are that the user won’t be redirect to different screens and got confused of what’s going on.
This window create two sign-in entities: one is simple A tag that demonstrates how we could start sign-in redirect process by simply redirect browser to authentication service, and the second one is a button, that does the same using form submit.
Callback function
callback.php
<html>
<head>
<style>
</style>
<script type = "text/javascript">
var id_token = "<?php echo $_POST['id_token'] ?>";
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
function sendDataToMainApp() {
var parsedToken = parseJwt(id_token);
window.opener.receiveUserSignedInData(parsedToken);
window.close();
}
</script>
</head>
<body onLoad="sendDataToMainApp()">
</body>
</html>
what we just did:
Were, we get id_token from Apple authentication service, that passes this via the request type that we selected (post, fragment, get) decodes, id_token then passes it to the parent window and closes itself.
<html>
<head>
<style>
h1 {
text-align: center;
}
#welcomePanel {
display: none;
text-align: center;
}
#signInPanel h2 {
text-align: center;
border: 1px solid silver;
}
#google-auth-button {
-webkit-box-align: baseline;
align-items: baseline;
border-width: 0px;
border-radius: 3px;
box-sizing: border-box;
display: inline-flex;
font-size: inherit;
font-style: normal;
font-family: inherit;
max-width: 100%;
position: relative;
text-align: center;
text-decoration: none;
transition: background 0.1s ease-out 0s, box-shadow 0.15s cubic-bezier(0.47, 0.03, 0.49, 1.38) 0s;
white-space: nowrap;
cursor: pointer;
padding: 0px 10px;
vertical-align: middle;
width: 100%;
-webkit-box-pack: center;
justify-content: center;
font-weight: bold;
color: var(--ds-text,#42526E) !important;
height: 40px !important;
line-height: 40px !important;
background: rgb(255, 255, 255) !important;
box-shadow: rgb(0 0 0 / 20%) 1px 1px 5px 0px !important;
}
</style>
<!-- First we have to include Apple Sign-in script -->
<script
type="text/javascript"
src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
</head>
<body>
<script>
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
/**
* This function will initialize the `AppleID.auth` object with parameter we pass in.
*/
const initApple = () => {
window.AppleID.auth.init({
clientId: "com.toni-develops.sign-in-with-apple-identifier", // This is the service ID we created.
scope: "name email", // To tell apple we want the user name and emails fields in the response it sends us.
redirectURI: "https://www.toni-develops.com/external-files/examples/oauth-with-apple-using-js-library", // As registered along with our service ID
state: "origin:web", // Any string of your choice that you may use for some logic. It's optional and you may omit it.
usePopup: true, // Important if we want to capture the data apple sends on the client side.
});
};
/**
* This function is where the magic happens.
* This is a simple example, ideally you'll have catch block as well to handle authentication failure.
*/
const singInApple = async () => {
const response = await window.AppleID.auth.signIn();
return response;
};
initApple();
function signInWithApple() {
const userData = singInApple();
userData.then( (data) => {
console.dir(data, { depth: null });
const result = parseJwt(data.authorization.id_token)
console.dir(result, { depth: null });
document.querySelector('#signInPanel').innerHTML = '<h2>Welcome ' + result.email + '</h2>';
});
}
</script>
<h1>Sign-In with Apple using Java Script library example</h1>
<div id="signInPanel">
<button id="google-auth-button" class="css-11s2kpt" type="button" tabindex="0" onclick="signInWithApple()">
<span class="css-1ujqpe8">
<img class="appleLogo" src="https://www.toni-develops.com/external-files/examples/assets/apple-logo.svg" alt="">
</span>
<span class="css-19r5em7"><span>Continue with Apple</span>
</button>
</div>
</body>
</html>
In this example we have the main app (in this case my blogpost) with a Log-in button (Log in Example, above ), that redirects to the Log-In app, which on the other hand redirects to selected Log-In service (Google, Apple, Facebook, Github) passing app id an all necessary log in data.
After the user grant permissions to share requested data (email, username, real name, etc.) called scope, authentication service is calling back our app service, passing code which our service is using to make HTTP request to the provider and exchange it for JWT (Jason Web Token) containing requested user information.
on Authorized Java Script Origins add the domain from where the WEB app will be served. You could use http://localhost if you like.
on Authorized Redirect URIs add authentication service callback script that Google will call to pass code that could be exchanged for JWT (JSON Web Token) containing user’s information requested.
On the top right corner you will see ClientID and App secret. Write them down since we are going to need them in our authentication service later on.
Create main WEB app and authentication service
We will create main WEB app and authentication service that main app will use. This way same authentication service could be used for different WEB aps.
(Main web app) with Log In button, that redirects to the authentication app.
Let’s get started by creating simple WEB app that will have Log In button, that will simply redirect to our log in service. I already did that in the “Log In Example” above.
index.html
<html>
<head>
<style>
...
</style>
</head>
<body>
<!-- there is no Java script involved at all -->
<div id="log_in_wrapper">
<button class='continue_with_google'>
<a href='https://log-in-service-app/index.html'>
Log In
</a>
</button>
</div>
</body>
</html>
Nothing fancy happens here. We have button that simply redirects to our authentication service, which draws log-in with buttons (with Google, Apple, Facebook, GitHub)
(Authentication app) that shows log-in with buttons (with Google, Apple, Facebook, GitHub)
index.php
<html>
<head>
<style>
...
</style>
</head>
<body>
<!-- there is no Java script involved at all -->
<div id="log_in_wrapper">
<button class='continue_with_google'>
<a href='step-1-auth-google-get-redirect.php'>
<img src="some-google-logo"/>
<span>Continue with Google</span>
</a>
</button>
</div>
</body>
</html>
This is just another Web app that shows log in buttons. We could skip this step and simply have this buttons in the main WEB app.
For simplicity I only added Log In With google.
This button redirects to step-1-auth-google-get-redirect.php the first part of our authentication service.
Creating config file
<?php
// Fill these out with the values you got from Google
$googleClientID = '989056576533-mtef8cl5is5ogjh3np580ireurns7l5k.apps.googleusercontent.com';
$googleClientSecret = 'xxxxxxxxxxxxxx';
// This is the URL we'll send the user to first to get their authorization
$authorizeURL = 'https://accounts.google.com/o/oauth2/v2/auth';
// This is Google's OpenID Connect token endpoint
$tokenURL = 'https://www.googleapis.com/oauth2/v4/token';
// The main application URL. Google will pass JWT token back to the main app
$baseURL = 'https://www.toni-develops.com/external-files/examples/oauth-with-google-with-redirect-step-by-step/step-2-auth-google-redirect.php';
$usserInfoURL = 'https://www.googleapis.com/oauth2/v3/userinfo';
// the initial app url
$initialAppURL = 'https://www.toni-develops.com/2023/01/05/using-oauth2-0-to-log-in-with-google-using-redirect/';
After selecting to log-in with desired authentication provider, call backend service to prepare all necessary parameters and call selected authentication service (Google, Apple, Facebook, GitHub)
step-1-auth-google-get-redirect.php
<?php
// add Google app config params
include 'google-config.php';
// Start a session so we have session id to make sure that the redicect is instantiated by this script
$sessionId = '123';
// ####################################################
// STEP 1: Start the login process by sending the user
// to Google's authorization page,
// and passing app params
// ####################################################
// Generate a random hash and store in the session to make sure that
//$_SESSION['state'] = bin2hex(random_bytes(16));
$params = array(
'response_type' => 'code',
'client_id' => $googleClientID,
'redirect_uri' => $baseURL,
'scope' => 'openid email profile',
'state' => $sessionId
);
// Redirect the user to Google's authorization page, passing the above parameters as a GET request
header('Location: ' . $authorizeURL . '?' . http_build_query($params));
This simply redirects to the authentication service. In this case Google, passing these parameters:
response_type – we requesting code in the response which we are going to exchange it for JWT
client_id – is the app id that we registered
redirect_url – is our authentication service url, that Google will call passing code parameter. Here we are going to exchange it for JWT In this example this is step-2-auth-google-redirect.php` script below.
scope – is the requested scope. In our example we just need user name and email: openid email profile
state – this is unique string identifier, that we will check in the callback to make sure that this sign-in workflow is originated by our script
Everything that happens in the above file, could be achieved by simply passing this url in the browser. If you examine the url, it simply passes the same get parameters and query string params.
Next step: Google calls our callback script, passing code which our script exchange for JWT containing user info
step-2-auth-google-redirect.php
<?php
// add Google app config params
include 'google-config.php';
// ####################################################
// Step 2: When Google redirects the user back here,
// there will be a "code" and "state"
// parameter in the query string
// Exchange the auth code for a JWT token,
// and extract requested user data from there
// ####################################################
if(isset($_GET['code'])) {
// Verify the state matches our stored state
if(!$_GET['state'] && $_GET['state'] !== '123') {
die('missing state!');
}
// Exchange the auth code for a token
$ch = curl_init($tokenURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'grant_type' => 'authorization_code',
'client_id' => $googleClientID,
'client_secret' => $googleClientSecret,
'redirect_uri' => $baseURL,
'code' => $_GET['code']
]));
$response = curl_exec($ch);
// $data will contain
// access_token,
// expires_in,
// scope,
// token_type,
// id_token - which is JWT token, containing user data according to requested scope from the initial script.
$data = json_decode($response, true);
//echo '<pre>';print_r($data);die("</pre>");
// Note: You'd probably want to use a real JWT library
// but this will do in a pinch. This is only safe to do
// because the ID token came from the https connection
// from Google rather than an untrusted browser redirect
// Split the JWT string into three parts
$jwt = explode('.', $data['id_token']);
// Extract the middle part, base64 decode it, then json_decode it
$IDToken = json_decode(base64_decode($jwt[1]), true);
// This step is required only if we want to get extra info
/*
$ch = curl_init($usserInfoURL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer '.$data['access_token']
]);
$userInfo = curl_exec($ch);
*/
/*
echo '<h1>Token ID</h1>';
echo '<pre>';print_r($IDToken);echo '</pre>';
echo '<h1>User info</h1>';
echo '<pre>';print_r($userInfo);die('</pre>');
*/
$params = array(
'email' => $IDToken['email'],
'user_name' => $IDToken['name'],
'given_name' => $IDToken['given_name'],
'family_name' => $IDToken['family_name']
);
// Redirect the user to the initial app passing user data as Query String parameters so the front end could use them.
header('Location: ' . $initialAppURL . '?' . http_build_query($params));
}
else {
echo 'Bad response. Missing `code` GET response';
}
line 19-30: we make curl request to Google service passing:
grant_type – authorization_code
client_id – App id
client_secret – App secret
redirect_uri – should be url registered in Authorized redirect URLs
code – this is the code returned by Google as GET parameter
Last step: We are redirecting back to the main app, passing parameters that front end needs (email, user name) as GET or POST params.
Additionally we could save the user into our database, or do some additional work before redirecting to the main app (line 76)